开发文档
使用 API 密钥接入 Pix聚合AI 的图片、视频与音频生成能力。所有示例均对应当前已实现接口,未开放能力会明确标记。
https://fei85.cn三步完成首次接入
- 01创建 API 密钥
登录控制台,在“API 密钥”中创建密钥并按需设置权限、有效期和额度上限。
- 02确认模型 ID
在创作控制台选择可用模型,接口参数使用模型的实际 ID,不使用展示名称。
- 03发送 HTTPS 请求
将密钥放入 Authorization 请求头,正文使用 UTF-8 JSON。
能力状态
这里区分已经通过 API 密钥开放的能力和仅在站内可用的能力。
POST /api/generate,已接入 Bearer Token。
站内已有 Chat 模型,但外部聊天接口尚未发布。
GET /api/check_record?id=记录ID,使用同一 Bearer Token 鉴权,可在无人值守服务中轮询。
当前没有对外发布 /v1/chat/completions。在正式开放前,请勿将站内 Chat 请求当作服务端 API 使用。
身份验证
API 密钥只应保存在服务端。每个请求都需要在 HTTP 请求头中携带 Bearer Token。
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
建议按应用分别创建密钥,只勾选需要的图片、视频或音频权限,并设置额度上限和有效期。
/api/generate提交图片、视频或音频生成任务。接口成功后返回记录 ID、初始状态与本次预计扣点。
curl -X POST 'https://fei85.cn/api/generate' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{"mode":"draw","model":"MODEL_ID","prompt":"一座未来城市的清晨,电影感光影"}'
const response = await fetch('https://fei85.cn/api/generate', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
"mode": "draw",
"model": "MODEL_ID",
"prompt": "一座未来城市的清晨,电影感光影"
})
});
const result = await response.json();
<?php
$payload = array (
'mode' => 'draw',
'model' => 'MODEL_ID',
'prompt' => '一座未来城市的清晨,电影感光影',
);
$request = curl_init('https://fei85.cn/api/generate');
curl_setopt_array($request, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer YOUR_API_KEY',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);
$response = curl_exec($request);
请求字段
modestring是draw、edit、video 或 audio。
modelstring是控制台中启用模型的实际 ID。
promptstring是生成提示词,使用 UTF-8 文本。
不同模型可能要求额外参数。以控制台该模型当前显示的参数为准;未知字段可能被忽略或返回 422。
响应结构
{
"ok": true,
"record_id": 1024,
"record_ids": [
1024
],
"status": "queued",
"status_url": "/api/check_record?id=1024",
"credits": {
"normal": 98.5,
"member": 0
},
"cost": 1.5,
"created": true
}
record_id本次任务的主记录 ID。
status初始状态,通常为 queued。
cost本次任务记录的扣点数。
created是否新建了任务记录。
响应会返回 status_url(即 /api/check_record?id=记录ID)。使用提交时相同的 Bearer Token 轮询该地址即可获取最新状态与结果地址;当 record.status 为 succeeded 时即可取用结果。
状态轮询
提交后使用响应里的 status_url 轮询任务进度。轮询接口同样需要 Bearer Token,可在服务端定时调用。
curl 'https://fei85.cn/api/check_record?id=记录ID' \
-H 'Authorization: Bearer YOUR_API_KEY'
record.statusqueued / running 继续轮询;succeeded 取用结果;failed 任务失败。
record.image_url / video_url / audio_url成功后的结果地址。
records批量任务的各子任务状态数组。
完整调用时序
从提交到取回结果的一次完整往返。提交与轮询使用同一个 Bearer Token。
客户端 灵智 AI 服务端
│ │
│ 1. POST /api/generate │
│ Authorization: Bearer YOUR_API_KEY │
│ { mode, model, prompt } │
├───────────────────────────────────────►│
│ │ 校验令牌 / 权限 / 预扣点
│ 2. 200 OK │
│ { record_id, status=queued, │
│ status_url, cost } │
│◄───────────────────────────────────────┤
│ │
│ 3. GET /api/check_record?id=记录ID │
│ Authorization: Bearer YOUR_API_KEY │
├───────────────────────────────────────►│
│ { status: running } │
│◄───────────────────────────────────────┤ ← 未完成,间隔重试
│ │
│ 3'. GET /api/check_record?id=记录ID │
├───────────────────────────────────────►│
│ { status: succeeded, │
│ image_url: "https://..." } │
│◄───────────────────────────────────────┤
│ │
│ 4. 取用 record.image_url / video_url │
└────────────────────────────────────────┘
第 1 步提交任务,拿到 record_id 与 status_url。
第 2 步服务端返回初始状态(通常 queued)与本次预计扣点 cost。
第 3 步用同一 Token 轮询 status_url;running 时按 3~5 秒间隔重试。
第 4 步status=succeeded 后从 image_url / video_url / audio_url 取结果。
不要高频轮询。建议在 queued / running 时每 3~5 秒查询一次,直到终态(succeeded / failed / cancelled)。单图通常数十秒,视频可能数分钟。
/api/generate第三方网站接入 Dola 全能30秒时,必须由服务端同时发送字符串模型键和两个数字模型 ID。任务采用异步受理,提交成功不立即返回视频地址。
ok=true + status=queued + record_id 表示任务已经成功受理。此时没有 video_url 是正常行为;保存 record_id 后再由 Worker 轮询,不能立即报“生成提供商未返回可用结果”。
modestring是固定 video。
modelstring是固定 dola-30s。
model_idinteger是当前固定 1;新网站上线前重新核对模型页。
ai_model_idinteger是当前固定 1,与 model_id 同时发送。
promptstring是非空 UTF-8 文本,描述内容、动作、镜头与风格。
durationinteger是固定整数 30,不要允许前端覆盖。
resolutionstring是固定字符串 720P。
ratiostring是使用下方支持画幅;未选择或 auto 时服务端改为 16:9。
aspect_ratiostring是与 ratio 保持一致的兼容字段。
sizestring是使用画幅对应的 720P 尺寸。
reference_image_urlsarray否0 至 9 张去重后的公网 HTTPS 参考图;纯文本任务建议省略。
FEI85_API_BASE=https://fei85.cn
FEI85_API_KEY=YOUR_API_KEY
FEI85_DOLA_MODEL=dola-30s
FEI85_DOLA_MODEL_ID=1
FEI85_DOLA_AI_MODEL_ID=1
不要把任意前端 JSON 原样透传给 fei85。服务端必须使用字段白名单,并强制覆盖模型、时长和清晰度。
参考图与画幅
Dola 支持纯文本生成,也支持 1 至 9 张参考图。参考图必须先由你的网站转换为 fei85 可持续访问的公网地址。
横屏16:91280x720
竖屏9:16720x1280
方形1:1720x720
竖向传统3:4720x960
横向传统4:3960x720
超宽屏21:91680x720
- 数量与去重最多 9 张,服务端去重;0 张时省略
reference_image_urls。 - 只允许公网 HTTPS拒绝 HTTP、
localhost、内网 IP、file://、浏览器blob:和带用户名密码的 URL。 - 保持持续可访问URL 在生成期间不能过期;短时签名地址必须覆盖排队和生成时长。
- 服务端校验下载前校验协议、主机及 DNS 解析结果,限制重定向并保持 TLS 证书校验开启。
可直接使用的提交与轮询示例
以下示例均只使用占位密钥。JavaScript 示例面向 Node.js 18+ 服务端,不能放入浏览器。
curl -X POST 'https://fei85.cn/api/generate' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Accept: application/json' \
-H 'Content-Type: application/json; charset=utf-8' \
--data-raw '{
"mode": "video",
"model": "dola-30s",
"model_id": 1,
"ai_model_id": 1,
"prompt": "保持人物身份与服装一致,人物转身看向镜头,镜头缓慢拉近",
"duration": 30,
"resolution": "720P",
"ratio": "9:16",
"aspect_ratio": "9:16",
"size": "720x1280",
"reference_image_urls": [
"https://media.example.com/reference-1.jpg",
"https://media.example.com/reference-2.jpg"
]
}'
# 保存提交响应中的 record_id;3 至 5 秒后使用同一 Token 查询。
curl 'https://fei85.cn/api/check_record?id=1024' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Accept: application/json'
<?php
declare(strict_types=1);
const FEI85_BASE = 'https://fei85.cn';
const DOLA_SIZES = [
'16:9' => '1280x720', '9:16' => '720x1280', '1:1' => '720x720',
'3:4' => '720x960', '4:3' => '960x720', '21:9' => '1680x720',
];
$apiKey = trim((string)getenv('FEI85_API_KEY'));
if ($apiKey === '' || preg_match('/[\x00-\x20\x7f]/', $apiKey)) {
throw new RuntimeException('FEI85_API_KEY is required.');
}
function fei85Request(string $apiKey, string $method, string $path, ?array $payload = null): array
{
$curl = curl_init(FEI85_BASE . $path);
$headers = ['Accept: application/json', 'Authorization: Bearer ' . $apiKey];
$options = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_TIMEOUT => $method === 'POST' ? 120 : 60,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_HTTPHEADER => $headers,
];
if ($payload !== null) {
$options[CURLOPT_POSTFIELDS] = json_encode(
$payload,
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_THROW_ON_ERROR
);
$options[CURLOPT_HTTPHEADER][] = 'Content-Type: application/json; charset=utf-8';
}
curl_setopt_array($curl, $options);
$body = curl_exec($curl);
$status = (int)curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
$error = curl_error($curl);
curl_close($curl);
if (!is_string($body)) throw new RuntimeException('fei85 transport error: ' . $error);
$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
if ($status < 200 || $status >= 300 || ($data['ok'] ?? false) !== true) {
throw new RuntimeException('fei85 HTTP ' . $status . ': ' . (string)($data['message'] ?? 'Request failed'));
}
return $data;
}
function dolaResultUrl(array $record): string
{
$media = is_array($record['media'] ?? null) ? $record['media'] : [];
foreach ([$record['video_url'] ?? '', $record['video_src'] ?? '', $record['media_src'] ?? '', $media['url'] ?? ''] as $value) {
$url = trim((string)$value);
if (str_starts_with($url, '/uploads/')) $url = FEI85_BASE . $url;
$parts = parse_url($url);
if (filter_var($url, FILTER_VALIDATE_URL) !== false
&& is_array($parts)
&& in_array(strtolower((string)($parts['scheme'] ?? '')), ['http', 'https'], true)
&& !isset($parts['user'], $parts['pass'])) {
return $url;
}
}
return '';
}
$ratio = '16:9';
$references = array_values(array_unique(['https://media.example.com/character.jpg']));
if (count($references) > 9) throw new InvalidArgumentException('Dola accepts at most 9 references.');
foreach ($references as $value) {
$parts = parse_url($value);
if (filter_var($value, FILTER_VALIDATE_URL) === false
|| !is_array($parts)
|| strtolower((string)($parts['scheme'] ?? '')) !== 'https'
|| isset($parts['user'], $parts['pass'])) {
throw new InvalidArgumentException('Reference images must use public HTTPS URLs.');
}
}
$submitted = fei85Request($apiKey, 'POST', '/api/generate', [
'mode' => 'video',
'model' => 'dola-30s',
'model_id' => 1,
'ai_model_id' => 1,
'prompt' => '保持主体身份一致,镜头平稳向前推进',
'duration' => 30,
'resolution' => '720P',
'ratio' => $ratio,
'aspect_ratio' => $ratio,
'size' => DOLA_SIZES[$ratio],
'reference_image_urls' => $references,
]);
$recordId = filter_var($submitted['record_id'] ?? null, FILTER_VALIDATE_INT);
if (!is_int($recordId) || $recordId < 1) throw new RuntimeException('Invalid record_id.');
// queued + record_id 表示成功受理;保存 record_id,再由 Worker 异步轮询。
$pending = ['queued', 'pending', 'running', 'processing'];
$success = ['succeeded', 'success', 'completed', 'finished', 'done'];
$failure = ['failed', 'failure', 'error', 'cancelled', 'canceled', 'refunded'];
$deadline = time() + 1200;
while (time() < $deadline) {
sleep(4);
$data = fei85Request($apiKey, 'GET', '/api/check_record?id=' . rawurlencode((string)$recordId));
$record = is_array($data['record'] ?? null) ? $data['record'] : [];
$status = strtolower(trim((string)($record['status'] ?? $record['status_key'] ?? '')));
if (in_array($status, $pending, true)) continue;
if (in_array($status, $success, true)) {
$url = dolaResultUrl($record);
if ($url === '') throw new RuntimeException('Task succeeded without a usable video URL.');
echo $url . PHP_EOL;
break;
}
if (in_array($status, $failure, true)) throw new RuntimeException('Dola generation failed.');
throw new RuntimeException('Unknown Dola status: ' . $status);
}
// Node.js 18+ 服务端示例。不要把 API Key 放进浏览器代码。
const API_BASE = 'https://fei85.cn';
const API_KEY = process.env.FEI85_API_KEY;
if (!API_KEY) throw new Error('FEI85_API_KEY is required');
const references = ['https://media.example.com/character.jpg'];
if (references.length > 9) throw new Error('Dola accepts at most 9 references');
for (const value of references) {
const url = new URL(value);
if (url.protocol !== 'https:' || url.username || url.password) {
throw new Error('Reference images must use public HTTPS URLs');
}
}
async function request(path, options = {}) {
const response = await fetch(API_BASE + path, {
...options,
headers: {
Accept: 'application/json',
Authorization: `Bearer ${API_KEY}`,
...(options.body ? {'Content-Type': 'application/json; charset=utf-8'} : {}),
},
});
const data = await response.json();
if (!response.ok || data.ok !== true) {
throw new Error(data.message || `fei85 HTTP ${response.status}`);
}
return data;
}
function resultUrl(record) {
const values = [
record.video_url,
record.video_src,
record.media_src,
record.media?.url,
];
for (let value of values) {
if (typeof value !== 'string' || !value.trim()) continue;
value = value.trim();
if (value.startsWith('/uploads/')) value = API_BASE + value;
if (!/^https?:\/\//i.test(value)) continue;
const url = new URL(value);
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) continue;
return url.href;
}
return '';
}
const submitted = await request('/api/generate', {
method: 'POST',
body: JSON.stringify({
mode: 'video',
model: 'dola-30s',
model_id: 1,
ai_model_id: 1,
prompt: '保持主体身份一致,镜头平稳向前推进',
duration: 30,
resolution: '720P',
ratio: '16:9',
aspect_ratio: '16:9',
size: '1280x720',
reference_image_urls: references,
}),
});
const recordId = Number(submitted.record_id);
if (!Number.isInteger(recordId) || recordId < 1) throw new Error('Invalid record_id');
// queued + record_id 就是成功受理;此时没有视频 URL 属于正常情况。
const pending = new Set(['queued', 'pending', 'running', 'processing']);
const succeeded = new Set(['succeeded', 'success', 'completed', 'finished', 'done']);
const failed = new Set(['failed', 'failure', 'error', 'cancelled', 'canceled', 'refunded']);
const deadline = Date.now() + 20 * 60 * 1000;
while (Date.now() < deadline) {
await new Promise(resolve => setTimeout(resolve, 4000));
const data = await request(`/api/check_record?id=${recordId}`);
const status = String(data.record?.status ?? data.record?.status_key ?? '').toLowerCase();
if (pending.has(status)) continue;
if (succeeded.has(status)) {
const url = resultUrl(data.record || {});
if (!url) throw new Error('Task succeeded without a usable video URL');
console.log({recordId, videoUrl: url});
break;
}
if (failed.has(status)) throw new Error(data.record?.error_message || 'Dola generation failed');
throw new Error(`Unknown Dola status: ${status}`);
}
/api/check_record?id={record_id}轮询时使用提交任务时相同的 Bearer Token。推荐每 3 至 5 秒查询一次,并在任何成功或失败终态立即停止。
pendingqueued / pending / running / processing3 至 5 秒后继续轮询。
succeededsucceeded / success / completed / finished / done停止轮询,按兼容顺序提取结果 URL。
failedfailed / failure / error / cancelled / canceled / refunded停止轮询,记录脱敏后的错误。
record.video_url第一优先级。
record.video_src第二优先级。
record.media_src第三优先级。
record.media.url第四优先级。
仅将 /uploads/... 基于 https://fei85.cn 补全为绝对地址。最终结果只接受 http 或 https URL,并拒绝本机、内网及带凭据的地址。
- 本地等待上限视频可设置 15 至 20 分钟等待上限;本地超时不代表上游失败,应保留
record_id继续后台查询。 - 只重试查询网络结果不明确时不要自动重复提交
POST /api/generate,否则可能重复创建任务和扣费。 - 幂等结算不要在
queued时退款,也不要仅凭前端提示结算;使用最终状态和本地业务规则幂等处理。
常见错误、安全发布与验收
固定字段属于当前已验证契约,但模型数字 ID、价格和可用状态可能调整。每个新网站上线前都要重新核对。
401Key 无效或过期检查 Bearer 格式、Key 状态与有效期。
402余额或额度不足检查账户余额、Key 额度和当前模型价格。
403缺少视频权限只增加所需的视频生成与记录查询权限。
409生成队列未启用联系平台支持,不要无限重试提交。
422模型或参数无效核对三个模型字段、固定时长、清晰度和画幅。
500服务异常保留请求时间与脱敏响应,受控退避后查询。
找不到模型遗漏数字模型 ID同时发送 model=dola-30s、model_id=1、ai_model_id=1。
没有结果把 queued 当失败提交阶段只要求有效 record_id,视频结果必须轮询。
- 01只读核对
确认生产版本、目标文件哈希、运行进程和当前模型 ID,不直接覆盖生产文件。
- 02隔离演练
在独立目录运行 PHP 语法、请求体、响应、轮询、URL 与安全契约测试。
- 03备份与原子替换
使用固定哈希门禁、独立备份、同目录临时文件和原子
rename;安装后哈希不符立即回滚。 - 04按运行方式重启 Worker
Supervisor、Laravel Queue、Symfony Messenger、Node.js、PM2、Swoole、RoadRunner 或容器常驻进程发布后必须 reload 或 restart。仅文档变更无需重启生成 Worker。
- 05发布后验收
核对新 PID、启动时间、RUNNING 状态、心跳、生产文件哈希和不访问上游的 Fixture 契约;页面检查桌面与手机无横向溢出。
真实生成会产生费用。未获业务负责人明确授权时,只做静态、Fixture、页面、无效占位 Token 和运行状态检查,不提交生成、不扣费、不支付、不退款。
- 密钥隔离API Key 只放服务端环境变量;禁止进入源码、前端、Git、日志和发布包。
- 日志脱敏不记录完整 Authorization、真实 Key 或敏感提示词;错误消息限制长度并清除 Token。
- TLS 与 URL保持证书与主机校验开启,拒绝私网 URL、危险重定向和带凭据地址。
- 版本漂移新接入前重新确认
model_id、ai_model_id、价格、画幅和参考图数量,并更新 Fixture 契约。
模型与能力
模型 ID、可用状态和参数由后台配置实时决定。不要把展示名称写死为模型 ID。
可通过创作控制台查看启用模型及其参数,随后使用实际模型 ID 调用 /api/generate。
当前站内共有 62 个 Chat 模型;外部聊天 API 尚未开放,因此本页不提供伪造的兼容调用示例。
错误码
401密钥无效检查 Authorization 格式、密钥状态和有效期。
402额度或余额不足检查密钥额度、账户余额与模型费用。
403权限不足为密钥增加对应生成权限,或更换密钥。
409生成队列未启用当前服务暂不可提交生成任务,请联系平台支持。
422参数错误检查 mode、模型 ID、提示词和模型特定参数。
500服务器错误保留请求时间和响应内容,稍后重试或联系支持。
计费与额度
任务费用按后台当前模型价格扣除账户点数。API 密钥额度用于限制该密钥累计用量,不替代账户余额。
不要在客户端缓存长期价格。模型价格和可用状态可能调整,正式执行前应以控制台当前信息为准。
安全建议
- 只在服务端保存密钥不要把密钥写入浏览器 JavaScript、移动端包或公开仓库。
- 使用环境变量为开发、测试、生产分别创建密钥,避免共用。
- 采用最小权限仅开放应用需要的接口,并设置额度和有效期。
- 定期轮换密钥疑似泄漏时立即禁用并创建新密钥。
- 脱敏日志日志中只记录密钥前后缀,不记录完整 Authorization 请求头。