以下示例默认运行在 PHP 后端、Laravel Controller、Laravel Job、WordPress 后端插件或队列 worker 中。不要把 Tapapi API Key 放进浏览器前端、移动端包、公开仓库或客户端配置。
环境变量
TAPAPI_API_KEY=sk-xxx
TAPAPI_BASE_URL=https://tapapi.ai/v1
.env,普通 PHP 项目可以通过服务器环境变量或配置文件读取。
Laravel 的 config/services.php 可以这样配置:
'tapapi' => [
'key' => env('TAPAPI_API_KEY'),
'base_url' => env('TAPAPI_BASE_URL', 'https://tapapi.ai/v1'),
],
原生 PHP 请求 Helper
<?php
function tapapi_json_request(string $path, array $payload, int $timeout = 120): array
{
$apiKey = getenv('TAPAPI_API_KEY');
$baseUrl = rtrim(getenv('TAPAPI_BASE_URL') ?: 'https://tapapi.ai/v1', '/');
$ch = curl_init($baseUrl . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HEADER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE),
CURLOPT_TIMEOUT => $timeout,
]);
$raw = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$curlError = curl_error($ch);
curl_close($ch);
if ($raw === false || $curlError) {
throw new RuntimeException('Tapapi request failed: ' . $curlError);
}
$headers = substr($raw, 0, $headerSize);
$body = substr($raw, $headerSize);
$requestId = null;
if (preg_match('/^X-Oneapi-Request-Id:\s*(.+)$/mi', $headers, $matches)) {
$requestId = trim($matches[1]);
}
$data = json_decode($body, true);
if (!is_array($data)) {
throw new RuntimeException('Tapapi returned invalid JSON: ' . $body);
}
if ($status >= 400) {
$error = $data['error'] ?? [];
$details = [
'status' => $status,
'request_id' => $requestId,
'type' => $error['type'] ?? null,
'code' => $error['code'] ?? ($data['code'] ?? null),
'message' => $error['message'] ?? ($data['message'] ?? $body),
];
throw new RuntimeException('Tapapi error: ' . json_encode($details, JSON_UNESCAPED_UNICODE));
}
return [
'status' => $status,
'request_id' => $requestId,
'data' => $data,
];
}
X-Oneapi-Request-Id、error.type、error.code 和 error.message。任务/视频类接口的错误也可能返回顶层 code、message、data,不要只保存一段错误文本。
原生 PHP 文本请求
<?php
$result = tapapi_json_request('/chat/completions', [
'model' => 'gpt-5.4',
'messages' => [
['role' => 'user', 'content' => '用一句话解释 Tapapi 是什么'],
],
]);
echo $result['data']['choices'][0]['message']['content'] ?? '';
原生 PHP Responses 请求
/v1/chat/completions 是 PHP 接入的默认主线。需要 Responses API 时,先确认模型详情和返回字段:
<?php
$result = tapapi_json_request('/responses', [
'model' => 'gpt-5.4',
'input' => '用一句话解释 Tapapi 是什么',
]);
echo $result['data']['output_text'] ?? '';
原生 PHP 图片生成
<?php
$result = tapapi_json_request('/images/generations', [
'model' => 'nano-banana-pro',
'prompt' => 'a clean product photo of a white ceramic mug',
'n' => 1,
'size' => '1024x1024',
'response_format' => 'url',
], 180);
if (empty($result['data']['data'])) {
throw new RuntimeException('Tapapi returned empty image data');
}
$item = $result['data']['data'][0];
$image = $item['url'] ?? $item['b64_json'] ?? null;
if (!$image) {
throw new RuntimeException('Tapapi returned image data without url or b64_json');
}
echo $image;
url 或 b64_json。生产环境建议把图片转存到自己的对象存储,不要长期依赖临时 URL。批量生成时还要记录请求 n、实际返回数量和成功转存数量;如果响应包含 metadata.tapapi_partial,按实际返回张数处理。
保存图片结果
<?php
function save_tapapi_image_item(array $item, string $path): void
{
if (!empty($item['url'])) {
$context = stream_context_create([
'http' => ['timeout' => 60],
]);
$bytes = file_get_contents($item['url'], false, $context);
if ($bytes === false) {
throw new RuntimeException('Image download failed');
}
file_put_contents($path, $bytes);
return;
}
if (!empty($item['b64_json'])) {
$bytes = base64_decode($item['b64_json'], true);
if ($bytes === false) {
throw new RuntimeException('Invalid b64_json image data');
}
file_put_contents($path, $bytes);
return;
}
throw new RuntimeException('image item has no url or b64_json');
}
save_tapapi_image_item($item, __DIR__ . '/output.png');
Laravel 文本请求
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
function parseTapapiLaravelError(Response $response): array
{
$body = $response->json() ?? [];
$error = $body['error'] ?? [];
return [
'status' => $response->status(),
'request_id' => $response->header('X-Oneapi-Request-Id'),
'type' => $error['type'] ?? null,
'code' => $error['code'] ?? ($body['code'] ?? null),
'message' => $error['message'] ?? ($body['message'] ?? $response->body()),
];
}
$response = Http::withToken(config('services.tapapi.key'))
->timeout(120)
->post(config('services.tapapi.base_url') . '/chat/completions', [
'model' => 'gpt-5.4',
'messages' => [
['role' => 'user', 'content' => '用一句话解释 Tapapi 是什么'],
],
]);
if ($response->failed()) {
throw new RuntimeException(
json_encode(parseTapapiLaravelError($response), JSON_UNESCAPED_UNICODE)
);
}
$text = $response->json('choices.0.message.content');
Laravel Responses 请求
use Illuminate\Support\Facades\Http;
$response = Http::withToken(config('services.tapapi.key'))
->timeout(120)
->post(config('services.tapapi.base_url') . '/responses', [
'model' => 'gpt-5.4',
'input' => '用一句话解释 Tapapi 是什么',
]);
if ($response->failed()) {
throw new RuntimeException(
json_encode(parseTapapiLaravelError($response), JSON_UNESCAPED_UNICODE)
);
}
$text = $response->json('output_text');
Laravel 图片生成
use Illuminate\Support\Facades\Http;
$response = Http::withToken(config('services.tapapi.key'))
->timeout(180)
->post(config('services.tapapi.base_url') . '/images/generations', [
'model' => 'nano-banana-pro',
'prompt' => 'a clean product photo of a white ceramic mug',
'n' => 1,
'size' => '1024x1024',
'response_format' => 'url',
]);
if ($response->failed()) {
throw new RuntimeException(
json_encode(parseTapapiLaravelError($response), JSON_UNESCAPED_UNICODE)
);
}
$item = $response->json('data.0');
if (!$item) {
throw new RuntimeException('Tapapi returned empty image data');
}
$image = $item['url'] ?? $item['b64_json'] ?? null;
if (!$image) {
throw new RuntimeException('Tapapi returned image data without url or b64_json');
}
Storage 把图片保存到 S3、R2、OSS、COS 或本地磁盘。前端只拿你自己的图片 URL,不要长期展示上游临时 URL。
视频 API
视频 API 当前不是默认全量开放能力。只有控制台可见模型、价格已确认、账号有权限时,才适合生产调用。 推荐流程:POST /v1/videos
-> GET /v1/videos/{task_id}
-> completed 后 GET /v1/videos/{task_id}/content
seconds、size、aspect_ratio、参考图字段直接复用到所有视频模型。更多见 视频 API 和 任务与输出。
生产建议
| 场景 | 建议 |
|---|---|
| WordPress / 传统 PHP | 后端保存 API Key,前端只调用自己的业务接口 |
| Laravel Queue | 批量任务放进 Queue / Job,限制 worker 并发 |
| 重试 | 只对 429、临时 5xx、网络中断做有限重试,并加随机抖动 |
| 幂等 | 每个业务任务保存自己的 task_id,避免重复点击或 worker 重启重复请求 |
| 日志 | 保存接口、模型、HTTP 状态码、request_id、错误字段和最终用量 |
| 图片保存 | 同时兼容 url 和 b64_json,成功后尽快转存 |
| 超时 | 文本建议 120s 左右,图片建议按业务设置 180s 左右;超时后先查状态和账单再补跑 |
| 视频 API | 受控开放,先看 视频 API,不要默认写进批量任务 |
下一步
| 场景 | 文档 |
|---|---|
| 底层 HTTP 规则 | HTTP REST |
| Python 完整示例 | Python |
| Node.js / TypeScript | Node.js / TypeScript |
| OpenAI SDK 迁移总览 | OpenAI SDK 迁移 |
| 错误与重试 | 错误码与重试 |
| 生产上线检查 | 上线 Checklist |