← 开源
orhanerday

open-ai

A community-driven PHP SDK for OpenAI.

AI EngineeringAccess modelsPHP
在 GitHub 打开
增长势头
+024 小时新增 Star0.0%
2.36k
Star
293
Fork
+-1
本周
23
贡献者
创建于 2021-12-21 · 更新于 2026-10-16 · 今日第 8362 名
主要开发者
orhanerdayjoaciradetch
README

OpenAI PHP Client

A community-maintained PHP client for the OpenAI REST API, with streaming support. Requires PHP 7.4+ and the native cURL and JSON extensions, with no third-party runtime dependencies.

See API_COVERAGE.md for endpoint coverage and MIGRATING.md for breaking changes from earlier versions.

Installation

composer require orhanerday/open-ai

Quick start

Set OPENAI_API_KEY in your environment.

Linux or macOS:

export OPENAI_API_KEY='your-api-key'

PowerShell:

$Env:OPENAI_API_KEY = "your-api-key"

Windows Command Prompt:

set "OPENAI_API_KEY=your-api-key"

Save this as example.php:

<?php

require __DIR__ . '/vendor/autoload.php';

use Orhanerday\OpenAi\OpenAi;

$open_ai = new OpenAi(getenv('OPENAI_API_KEY'));
$result = $open_ai->chat([
    'model' => 'gpt-4o-mini',
    'messages' => [['role' => 'user', 'content' => 'Hello']],
]);

$response = json_decode($result, true, 512, JSON_THROW_ON_ERROR);
echo $response['choices'][0]['message']['content'];

Run it with php example.php. See Handling results for HTTP errors.

Any other endpoint

The SDK has dedicated methods for the most common APIs (see Supported APIs).

Use request() for REST endpoints without dedicated methods, such as Containers, Evals, organization administration, and Vaults. It supports JSON and multipart requests under the configured API prefix. WebSocket connections and workload identity token exchange need a separate client.

public function request(
    string $method,          // GET, POST, PUT, PATCH or DELETE (case-insensitive)
    string $path,            // relative path starting with "/", e.g. "/containers/cntr_123"
    ?array $opts = null,     // query parameters (GET) or request body (other methods)
    ?callable $stream = null,// required when the request streams
    bool $multipart = false  // force multipart/form-data
);

How it works

request() runs through the same code as every built-in method. That means:

  • The full URL is base URL + /v1 + path, so request('GET', '/models') calls https://api.openai.com/v1/models.
  • Your API key, setORG(), setHeader(), setProxy(), setTimeout(), and setCURLOptions() apply.
  • setBaseURL() and setApiVersion() apply, so it also works with OpenAI-compatible providers.
  • Ordinary calls return the raw response body as a string. Streaming calls deliver chunks to the callback; decode JSON responses with json_decode().
$method What $opts becomes
GET Query string. Nested arrays are encoded as filter[key]=value, lists as include[]=a&include[]=b, booleans as true/false.
POST, PUT, PATCH JSON body. An empty array sends {}.
DELETE No body unless you pass $opts.
Non-GET, with $multipart = true or a file/image key multipart/form-data body.

Examples

Create, list, and delete a resource:

// POST with a JSON body
$created = json_decode($open_ai->request('POST', '/containers', ['name' => 'my-container']), true);

// GET with query parameters: /v1/containers?limit=10&order=desc
$list = json_decode($open_ai->request('GET', '/containers', ['limit' => 10, 'order' => 'desc']), true);

// DELETE a resource by ID
$open_ai->request('DELETE', '/containers/' . $created['id']);

Evals:

$evals = json_decode($open_ai->request('GET', '/evals', ['limit' => 5]), true);

Organization administration (requires an admin key; replace the key with an admin key for these calls):

$admin = new OpenAi(getenv('OPENAI_ADMIN_KEY'));
$projects = json_decode($admin->request('GET', '/organization/projects', ['limit' => 20]), true);

Query parameters with arrays and booleans:

// /v1/responses/resp_123?include[]=message.output_text.logprobs&stream=false
$open_ai->request('GET', '/responses/resp_123', [
    'include' => ['message.output_text.logprobs'],
    'stream' => false,
]);

Streaming. Pass a callback as the fourth argument. It receives raw server-sent event chunks and must return the number of bytes it handled:

$open_ai->request('POST', '/responses', [
    'model' => 'gpt-4o-mini',
    'input' => 'Write a haiku about PHP.',
    'stream' => true,
], function ($curl, $chunk) {
    echo $chunk;

    return strlen($chunk);
});

File upload. A file key switches to multipart automatically. Pass true as the fifth argument when your body needs multipart without a file or image key:

$open_ai->request('POST', '/files', [
    'purpose' => 'user_data',
    'file' => new CURLFile('/path/to/data.jsonl'),
]);

Error handling

request() throws an Exception for an unsupported method, an invalid path, a missing API key, or a cURL failure such as a timeout. By default, HTTP errors from the API (4xx and 5xx) return their response bodies; check the status code and the error field, or enable setThrowOnError(true):

try {
    $body = json_decode($open_ai->request('GET', '/containers/unknown'), true);
    $status = $open_ai->getCURLInfo()['http_code'];

    if ($status >= 400) {
        echo "API error $status: " . ($body['error']['message'] ?? 'unknown error');
    }
} catch (Exception $e) {
    echo 'Request failed: ' . $e->getMessage();
}

Security notes

  • Paths must start with a single /. Absolute URLs, //host/... paths, and any path containing :// are rejected. To target another host, call setBaseURL() once.
  • Do not pass untrusted user input directly as $path. Validate or encode IDs first, for example with rawurlencode().

Finding the right route

Look up the path, method, parameters, and any required beta headers in the API reference. Dedicated methods and transport limitations are listed in API_COVERAGE.md.

Supported APIs

See API_COVERAGE.md for exact routes, guide support, and testing limits. WebSocket mode and mid-turn steering require a separate WebSocket client.

Configuration

The examples below use an OpenAi instance:

use Orhanerday\OpenAi\OpenAi;

$open_ai = new OpenAi(getenv('OPENAI_API_KEY'));

You can also configure or replace the key after construction:

$open_ai = new OpenAi();
$open_ai->setApiKey(getenv('OPENAI_API_KEY'));

setApiKey() replaces the Authorization header and rejects blank keys. Requests fail locally if no key is configured.

Organization

Specify the organization for API requests:

$open_ai->setORG('org-your-organization');

Base URL

Set the origin for an OpenAI-compatible API:

$open_ai->setBaseURL('https://ai.example.com');

Custom API Version

By default, the client appends /v1 to the Base URL. You can override this to support alternative API versions (e.g., ByteDance Ark uses /api/v3):

$open_ai->setBaseURL('https://ark.cn-beijing.volces.com');
$open_ai->setApiVersion('api/v3');

Proxy

Route requests through a proxy:

$open_ai->setProxy("http://127.0.0.1:1086");

Custom cURL options

Set cURL options for each request:

$open_ai->setCURLOptions([
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT => 300,
]);

Headers

$open_ai->setHeader(["Connection: keep-alive"]);

Request information

Inspect transport metadata after a request:

$open_ai->listModels();
$info = $open_ai->getCURLInfo();
echo $info['http_code'];

Chat completions

$result = $open_ai->chat([
    'model' => 'gpt-4o-mini',
    'messages' => [['role' => 'user', 'content' => 'Hello']],
]);

Read response text

$response = json_decode($result, true, 512, JSON_THROW_ON_ERROR);
echo $response['choices'][0]['message']['content'];

Stream example

Both chat() and createResponse() accept a callback when stream is true. The callback receives raw server-sent event chunks and must return the number of bytes consumed. Forward chunks unchanged so event boundaries survive browser parsing.

$open_ai = new OpenAi(getenv('OPENAI_API_KEY'));

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');

$open_ai->createResponse([
    'model' => 'gpt-4o-mini',
    'input' => 'Hello',
    'stream' => true,
    'store' => false,
], function ($curl, $data) {
    echo $data;
    if (ob_get_level() > 0) {
        ob_flush();
    }
    flush();

    return strlen($data);
});

For a browser client, listen for the named Responses events:





See the official streaming guide for event handling.

Images

Use a supported GPT Image model explicitly. GPT Image responses contain base64 image data. See the official image guide for supported models and parameters.

Create image

$result = $open_ai->image([
    'model' => 'gpt-image-2.5-flare',
    'prompt' => 'A cat drinking milk',
    'n' => 1,
    'size' => '1024x1024',
]);

$image = json_decode($result, true, 512, JSON_THROW_ON_ERROR);
file_put_contents('cat.png', base64_decode($image['data'][0]['b64_json']));

Create image edit

Supply your own input image. A single local file is sent as multipart form data. For multiple inputs, use the JSON images field with uploaded file IDs or image URLs.

$result = $open_ai->imageEdit([
    'model' => 'gpt-image-2.5-flare',
    'image' => new CURLFile('/path/to/input.png', 'image/png', 'input.png'),
    'prompt' => 'Add a beret to the animal in this image',
    'n' => 1,
    'size' => '1024x1024',
]);

To create a variation, use imageEdit() with a prompt describing the desired changes.

Embeddings

Create a vector representation of text.

Related guide: Embeddings

Create embeddings

$result = $open_ai->embeddings([
    "model" => "text-embedding-3-small",
    "input" => "The food was delicious and the waiter..."
]);

Content Moderations

Classify content using the moderation endpoint.

$flags = $open_ai->moderation([
    'model' => 'omni-moderation-latest',
    'input' => 'I hate ducks!'
]);

API reference: OpenAI Moderations

Audio

Text to speech (TTS)

tts($opts) wraps /v1/audio/speech for existing integrations. Its currently documented speech models are deprecated; see OpenAI's deprecations and the Realtime guide when planning a new voice integration. Pass a model supported by the speech endpoint explicitly; Realtime models use a different API.

Create transcription

Transcribe a recording using a file you supply:

$result = $open_ai->transcribe([
    'model' => 'gpt-transcribe',
    'file' => new CURLFile('/path/to/recording.mp3', 'audio/mpeg', 'recording.mp3'),
]);

See the official file transcription guide.

Create translation

translate($opts) wraps /v1/audio/translations for existing integrations. For new workflows, transcribe with gpt-transcribe, then send the transcript to createResponse() with translation instructions. Check the translation endpoint's supported models before using the compatibility wrapper.

Files

Upload files for model input, vector store file search, batch processing, or fine-tuning. Choose a purpose accepted by the Files API.

List files

Returns a list of files that belong to the user's organization.

$files = $open_ai->listFiles();

Upload file

Upload a file you supply, and retain the returned ID for later operations. For larger files, use the Uploads API. See the Files reference for current size limits.

$result = $open_ai->uploadFile([
    'purpose' => 'user_data',
    'file' => new CURLFile('/path/to/document.txt', 'text/plain', 'document.txt'),
]);

$uploaded = json_decode($result, true, 512, JSON_THROW_ON_ERROR);
$fileId = $uploaded['id'];

Upload file with an HTML form


    Select file to upload:
    
    

<?php
require __DIR__ . '/vendor/autoload.php';

use Orhanerday\OpenAi\OpenAi;

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    ob_clean();
    $open_ai = new OpenAi(getenv('OPENAI_API_KEY'));
    $tmp_file = $_FILES['fileToUpload']['tmp_name'];
    $file_name = basename($_FILES['fileToUpload']['name']);
    $c_file = curl_file_create($tmp_file, $_FILES['fileToUpload']['type'], $file_name);

    echo "[";
    echo $open_ai->uploadFile(
        [
            "purpose" => "user_data",
            "file" => $c_file,
        ]
    );
    echo ",";
    echo $open_ai->listFiles();
    echo "]";

}

Delete file

$result = $open_ai->deleteFile($fileId);

Retrieve file

$file = $open_ai->retrieveFile($fileId);

Retrieve file content

Use an ID for a downloadable file purpose, such as a fine-tuning or batch file. OpenAI rejects content downloads for user_data files, even though retrieving their metadata succeeds.

$file = $open_ai->retrieveFileContent($downloadableFileId);

Fine-tunes

These methods manage /v1/fine_tuning/jobs. Fine-tuning access is restricted by OpenAI's availability policy. Use a model supported for fine-tuning and a training file containing valid chat-format examples.

Create fine-tune

$upload = $open_ai->uploadFile([
    'purpose' => 'fine-tune',
    'file' => new CURLFile('/path/to/training.jsonl', 'application/jsonl', 'training.jsonl'),
]);
$trainingFileId = json_decode($upload, true, 512, JSON_THROW_ON_ERROR)['id'];

$result = $open_ai->createFineTune([
    'model' => 'gpt-4o-mini',
    'training_file' => $trainingFileId,
]);
$jobId = json_decode($result, true, 512, JSON_THROW_ON_ERROR)['id'];

List fine-tune

$jobs = $open_ai->listFineTunes();

Retrieve fine-tune

$job = $open_ai->retrieveFineTune($jobId);

Cancel fine-tune

$result = $open_ai->cancelFineTune($jobId);

List fine-tune events

$events = $open_ai->listFineTuneEvents($jobId);

Delete fine-tune model

Delete the model ID returned by a completed job, using DELETE /v1/models/{model}:

$completedJob = json_decode($open_ai->retrieveFineTune($jobId), true, 512, JSON_THROW_ON_ERROR);
if (!empty($completedJob['fine_tuned_model'])) {
    $result = $open_ai->deleteFineTune($completedJob['fine_tuned_model']);
}

Checkpoint permissions

Construct a separate OpenAi client with an admin API key for checkpoint permissions. listFineTuningCheckpointPermissions(), createFineTuningCheckpointPermission(), and deleteFineTuningCheckpointPermission() manage access across projects; listFineTuningCheckpoints() lists a job's checkpoints.

Models

List or retrieve model metadata.

List models

$result = $open_ai->listModels();

Retrieve model

$result = $open_ai->retrieveModel("gpt-4o-mini");

Handling results

By default, nonstreaming methods return the raw response body as a string, and HTTP API errors (4xx/5xx) do not throw exceptions. JSON, downloaded files, and audio keep their original format.

1. Structured Response Object

Inspect the most recent response via getLastResponse():

$raw = $open_ai->chat([
    'model' => 'gpt-4o-mini',
    'messages' => [['role' => 'user', 'content' => 'Hello']],
]);

$response = $open_ai->getLastResponse();

if ($response->ok()) {
    $data = $response->json(); // Decoded JSON array
    echo $data['choices'][0]['message']['content'];
} else {
    echo "HTTP Status: " . $response->status() . "\n";
    echo "Error Body: " . $response->body() . "\n";
}

// Access headers (case-insensitive)
echo $response->header('openai-processing-ms');

The Response object provides:

  • $response->status(): HTTP status code integer
  • $response->ok(): true for 2xx status codes
  • $response->json(bool $assoc = true): Decodes JSON directly
  • $response->body(): Raw response body string
  • $response->headers(): Headers from the final HTTP response, keyed by lowercase names
  • $response->header(string $name): Case-insensitive header lookup

Header collection also works when you configure your own CURLOPT_HEADERFUNCTION; the callback still runs and must return the number of bytes consumed. Successful streaming responses deliver their body to the callback, so Response::body() is empty. HTTP error bodies remain available in Response and OpenAiException.

getLastResponse() returns null before the first completed request. A transport failure leaves the previous response available; catch the exception before treating that response as the current result. json() throws JsonException for an empty body, binary download, or other invalid JSON.

2. Automatic Exception Handling (setThrowOnError)

If you prefer exceptions over manual status code checking, enable setThrowOnError(true). When enabled, any HTTP 4xx or 5xx status throws an OpenAiException:

use Orhanerday\OpenAi\OpenAiException;

$open_ai->setThrowOnError(true);

try {
    $raw = $open_ai->chat([
        'model' => 'gpt-4o-mini',
        'messages' => [['role' => 'user', 'content' => 'Hello']],
    ]);
} catch (OpenAiException $e) {
    echo "HTTP Status: " . $e->getStatusCode() . "\n";
    echo "Error Type: " . $e->getErrorType() . "\n";   // e.g. 'invalid_request_error'
    echo "Error Code: " . $e->getErrorCode() . "\n";   // e.g. 'rate_limit_exceeded'
    echo "Message: " . $e->getMessage() . "\n";
    print_r($e->getApiError());                       // Decoded error object
} catch (Exception $e) {
    // Network / cURL failures (e.g. timeout, connection dropped)
    echo "Connection error: " . $e->getMessage();
}

3. Automatic Retries & Rate-Limit Backoff

Enable automatic retries for rate limits (HTTP 429) and transient server errors (HTTP 500, 502, 503, 504 on idempotent requests):

// Retry up to 3 times, starting with a 500ms exponential backoff
$open_ai->setRetries(3, 500);
  • maxRetries counts extra attempts after the initial request; 0 disables retries.
  • Uses exponential backoff with small random jitter, capped at 60 seconds. Numeric and HTTP-date Retry-After values set a minimum wait. A server-requested wait above 60 seconds ends automatic retrying and returns the error for the application to handle later.
  • Retries rate-limit HTTP 429 responses, excluding billing/quota exhaustion. Retries HTTP 500, 502, 503, and 504 only for GET, PUT, and DELETE, using the effective method after custom cURL options. POST and PATCH are not retried on server errors.
  • Disables retries for streaming, response-body/upload/read callbacks, custom output files, and requests with CURLOPT_RETURNTRANSFER disabled. These transfers cannot safely be replayed.

Responses API

$result = $open_ai->createResponse([
    "model" => "gpt-4o-mini",
    "input" => "Hello",
    "max_output_tokens" => 100,
]);

Token counting and explicit compaction use countResponseInputTokens($opts) and compactResponse($opts). File inputs, tools, structured output, conversation state, automatic compaction, and background mode are passed through as request options; see API_COVERAGE.md. To resume a response created with background => true and stream => true:

$open_ai->retrieveResponse($responseId, [
    'stream' => true,
    'starting_after' => $lastSequenceNumber,
], function ($curl, $data) {
    echo $data;
    return strlen($data);
});

For the Responses multi-agent HTTP beta, add its header with $open_ai->setHeader(['OpenAI-Beta' => 'responses_multi_agent=v1']) and pass the documented multi_agent options to createResponse(). The application handles tool execution and SSE events.

Conversations API

$result = $open_ai->createConversation();

Agents API

Run hosted agents in sandboxed environments. The Agents API is in beta. Every method sends the OpenAI-Beta: agents=v1 header for that request only, and your own headers are restored afterwards. See the Agents guide for models, environments, and tools. With environment.type set to openai_hosted, OpenAI runs the sandbox. Self-hosted environments require your own executor; custom function tools are handled by your application.

Create a session and read the result

Create a session with stream enabled and pass a callback. It receives raw server-sent event chunks and must return the number of bytes it handled:

$open_ai->createAgentSession([
    'agent' => [
        'model' => 'gpt-6-astra',
        'instructions' => 'Write clean code, run it, and report the actual output.',
    ],
    'environment' => ['type' => 'openai_hosted'],
    'input' => 'Create tree.py, a script that prints the files in this directory. Run it and show the output.',
    'stream' => true,
], function ($curl, $chunk) {
    echo $chunk;

    return strlen($chunk);
});

For follow-up input, create an idle session and share its ID with a subscriber and a sender:

$session = json_decode($open_ai->createAgentSession([
    'agent' => ['model' => 'gpt-6-astra'],
    'environment' => ['type' => 'openai_hosted'],
]), true, 512, JSON_THROW_ON_ERROR);
$sessionId = $session['id'];
echo $sessionId . PHP_EOL;

Run the subscriber in a separate process with this ID. Wait until its event-stream connection is established before running the sender. Streams deliver live events and do not replay missed events:

$sessionId = getenv('OPENAI_AGENT_SESSION_ID'); // ID from session creation
$open_ai->streamAgentSessionEvents($sessionId, function ($curl, $chunk) {
    echo $chunk;

    return strlen($chunk);
});

Send input and approve actions

Run this sender in another process after the subscriber connects. The body is {"events": [...]}.

$sessionId = getenv('OPENAI_AGENT_SESSION_ID');
$open_ai->sendAgentSessionEvents($sessionId, [
    [
        'type' => 'agent.session.input.message',
        'input' => [[
            'role' => 'user',
            'content' => [['type' => 'input_text', 'text' => 'Now add a --depth option.']],
        ]],
    ],
]);

Other input events include agent.session.input.cancel, agent.session.input.tool_result, and agent.session.input.computer_use_approval_request_result. See the session events guide for payloads.

Manage sessions

$open_ai->listAgentSessions(['limit' => 20, 'order' => 'desc']);
$open_ai->retrieveAgentSession($sessionId);
$open_ai->updateAgentSession($sessionId, ['agent' => ['reasoning' => ['effort' => 'low']]]);
$open_ai->listAgentSessionItems($sessionId, ['order' => 'asc', 'limit' => 100]);
$open_ai->listAgentSessionTurns($sessionId, ['limit' => 20]);
$open_ai->retrieveAgentSessionTurn($sessionId, 'turn_123');
$open_ai->exportAgentSessionTraces($sessionId, ['limit' => 20]); // OTLP JSON; needs trace export enabled
$open_ai->deleteAgentSession($sessionId); // cancel active execution before deleting

Environments

Prewarming requires access to the prewarming beta. Prepare a workspace before attaching it to a session; unattached environments expire after five minutes. Supply any files the task needs:

$environment = json_decode($open_ai->createAgentEnvironment([
    'environment' => [
        'type' => 'openai_hosted',
        'packages' => ['python' => ['pandas==2.2.3']],
        'files' => [[
            'type' => 'inline',
            'path' => '/workspace/data.csv',
            'data' => base64_encode("category,amount\nbooks,10\nbooks,20\n"),
        ]],
    ],
]), true, 512, JSON_THROW_ON_ERROR);

$open_ai->createAgentSession([
    'agent' => ['model' => 'gpt-6-astra'],
    'environment' => ['type' => 'openai_hosted', 'environment_id' => $environment['id']],
    'input' => 'Use pandas to summarize /workspace/data.csv.',
    'stream' => true,
], function ($curl, $chunk) {
    echo $chunk;

    return strlen($chunk);
});

$open_ai->listAgentEnvironments();
$open_ai->retrieveAgentEnvironment($environment['id']);

Request bodies are passed through unchanged, so new beta fields work without an SDK update. Session IDs are URL-encoded for you. Model names, environment options, and event types change during the beta; check the guide for current values.

Vector Stores API

$result = $open_ai->createVectorStore([
    "name" => "Support FAQs"
]);

Batches API

$upload = $open_ai->uploadFile([
    'purpose' => 'batch',
    'file' => new CURLFile('/path/to/requests.jsonl', 'application/jsonl', 'requests.jsonl'),
]);
$batchFileId = json_decode($upload, true, 512, JSON_THROW_ON_ERROR)['id'];

$result = $open_ai->createBatch([
    "completion_window" => "24h",
    "endpoint" => "/v1/responses",
    "input_file_id" => $batchFileId,
]);

Uploads API

Create an upload, add its bytes as multipart parts, then complete it with the ordered part IDs. Each part must be at most 64 MB. This example uploads one part:

$path = '/path/to/document.txt';
$upload = json_decode($open_ai->createUpload([
    'filename' => 'document.txt',
    'purpose' => 'assistants', // Accepted for vector store files.
    'bytes' => filesize($path),
    'mime_type' => 'text/plain',
]), true, 512, JSON_THROW_ON_ERROR);

$part = json_decode($open_ai->addUploadPart($upload['id'], [
    'data' => new CURLFile($path, 'application/octet-stream', 'part.bin'),
]), true, 512, JSON_THROW_ON_ERROR);

$result = $open_ai->completeUpload($upload['id'], ['part_ids' => [$part['id']]]);

Realtime API

Create a short-lived client secret with a nested session configuration:

$result = $open_ai->createRealtimeClientSecret([
    'session' => [
        'type' => 'realtime',
        'model' => 'gpt-realtime-2.1-mini',
        'instructions' => 'You are a helpful assistant.',
    ],
    'expires_after' => ['anchor' => 'created_at', 'seconds' => 600],
]);

createRealtimeTranslationClientSecret() issues translation session credentials. createRealtimeCall() accepts an SDP offer and a session array, returning the raw SDP answer. acceptRealtimeCall(), hangupRealtimeCall(), referRealtimeCall(), and rejectRealtimeCall() control existing calls. These REST wrappers do not implement a WebRTC media or WebSocket client.

See the client-secret API reference.

Decisions API

$result = $open_ai->createDecision([
    'model' => 'gpt-6-luna',
    'input' => 'The support desk mascot is a purple owl.',
    'questions' => [[
        'type' => 'predicate',
        'name' => 'owl',
        'instructions' => 'The mascot is an owl.',
    ]],
]);

See the Decisions guide for beta availability and question types.

Webhooks

Use createWebhookEndpoint(), listWebhookEndpoints(), retrieveWebhookEndpoint(), updateWebhookEndpoint(), deleteWebhookEndpoint(), rotateWebhookEndpointSecret(), and testWebhookEndpoint() to manage subscriptions. Testing an endpoint sends an event to its configured URL. These methods use /v1/webhook_endpoints.

Verify incoming events using the original request body and the webhook signing secret:

use Orhanerday\OpenAi\Webhook;

$event = Webhook::unwrap(
    file_get_contents('php://input'),
    getallheaders(),
    getenv('OPENAI_WEBHOOK_SECRET')
);

Verification throws on invalid signatures, missing headers, or timestamps outside the default five-minute tolerance. Your application should handle retries idempotently using the webhook ID. See the webhooks guide.

Usage and Costs

Use an organization admin API key for these read-only reporting endpoints:

$reporting = new OpenAi(getenv('OPENAI_ADMIN_KEY'));
$options = [
    'start_time' => time() - 7 * 86400,
    'end_time' => time(),
    'bucket_width' => '1d',
    'group_by' => ['model', 'project_id'],
    'batch' => false,
    'limit' => 7,
];

$page = json_decode($reporting->getCompletionsUsage($options), true, 512, JSON_THROW_ON_ERROR);
if ($reporting->getCURLInfo()['http_code'] !== 200) {
    throw new RuntimeException('Usage request failed; check the decoded API error.');
}

// Each entry in data is a time bucket containing aggregated results.
foreach ($page['data'] as $bucket) {
    foreach ($bucket['results'] as $result) {
        echo $result['input_tokens'] . PHP_EOL;
    }
}

// To fetch the next page, preserve the filters and pass the returned cursor.
if ($page['has_more']) {
    $options['page'] = $page['next_page'];
    $nextPage = $reporting->getCompletionsUsage($options);
}

All methods require an options array with start_time (Unix seconds). Filters, array-valued group_by, time buckets, limits, and the page cursor pass through as GET query parameters. Each method returns the raw response body; pagination is controlled by the caller.

Report Method
Completions getCompletionsUsage($options)
Decisions getDecisionsUsage($options)
Embeddings getEmbeddingsUsage($options)
Moderations getModerationsUsage($options)
Images getImagesUsage($options)
Audio speeches getAudioSpeechesUsage($options)
Audio transcriptions getAudioTranscriptionsUsage($options)
Vector stores getVectorStoresUsage($options)
Code interpreter sessions getCodeInterpreterSessionsUsage($options)
File search calls getFileSearchCallsUsage($options)
Web search calls getWebSearchCallsUsage($options)
Costs getCosts($options)

For costs, use the same time-range and pagination fields, with cost-specific grouping:

$costs = $reporting->getCosts([
    'start_time' => time() - 7 * 86400,
    'bucket_width' => '1d',
    'group_by' => ['project_id', 'line_item'],
]);

Costs supports daily buckets and should be used for spend reporting. Usage supports minute, hour, and day buckets; usage aggregates may differ from billing totals. Filters and grouping fields vary by endpoint. See the official Usage and Costs reference. The /organization/usage/completions reporting route remains supported independently of the removed legacy text-generation integration.

Testing

Composer selects a compatible Pest version for your PHP runtime. The library continues to support PHP 7.4+.

Run the actual API tests with a key from the environment or your local .env:

export OPENAI_API_KEY='your-api-key'
composer test

composer test-live runs only live API tests. Without a key, API tests are explicitly skipped. Usage and Costs tests require OPENAI_ADMIN_KEY; run them separately with vendor/bin/pest tests/UsageLiveTest.php. Without that admin key, those cases are explicitly skipped. To run native cURL transport and webhook signature checks without API calls:

vendor/bin/pest --exclude-group=live

There are no cURL mocks. Live tests may incur API charges. They create and delete their own files, Responses, conversations, stored chats, uploads, and vector stores. They never launch a training job. Stored chat tests poll briefly because persistence can be asynchronous.

Models can be selected with OPENAI_CHAT_MODEL, OPENAI_IMAGE_MODEL, OPENAI_COMPACTION_MODEL, OPENAI_DECISION_MODEL, and OPENAI_REALTIME_MODEL. Defaults follow the examples and current guides. Additional account-dependent tests and required fixtures are documented in API_COVERAGE.md. Push/PR CI runs native transport and signature checks on PHP 7.4 and 8.4; manually dispatch the Tests workflow with an OPENAI_API_KEY repository secret to run the live suite. Add the optional OPENAI_ADMIN_KEY secret to include organization Usage and Costs tests.

Changelog

See CHANGELOG.md.

Contributing

See CONTRIBUTING.md.

Security Vulnerabilities

Report security vulnerabilities to orhanerday@gmail.com

Credits

License

MIT.

排名相邻
同类项目