PHP SDK for the WaveSpeed inference platform
π Visit wavespeed.ai β’ π Documentation β’ π¬ Issues
A port of the official WaveSpeed Python SDK
to PHP, with the same API surface and the same retry semantics. Zero runtime
dependencies β just ext-curl and ext-json.
- PHP 8.1 or newer
ext-curl,ext-json
composer require wavespeedai/wavespeed-phpRun WaveSpeed AI models with a simple API:
use WaveSpeed\WaveSpeed;
$output = WaveSpeed::run(
'wavespeed-ai/z-image/turbo',
['prompt' => 'Cat'],
);
echo $output['outputs'][0]; // Output URLSet your API key through the environment (get one at wavespeed.ai/accesskey):
export WAVESPEED_API_KEY="your-api-key"Or pass it directly:
use WaveSpeed\Client;
$client = new Client(apiKey: 'your-api-key');
$output = $client->run('wavespeed-ai/z-image/turbo', ['prompt' => 'Cat']);Or configure it once during bootstrap, so every client picks it up:
use WaveSpeed\Config;
Config::set(Config::API_KEY, $_ENV['WAVESPEED_API_KEY']);$output = $client->run(
'wavespeed-ai/z-image/turbo',
['prompt' => 'Cat'],
timeout: 300.0, // Max wait for completion; null (default) = no local limit
pollInterval: 1.0, // Status check interval (default: 1.0)
enableSyncMode: false, // Best-effort sync result attempt (default: false)
maxRetries: null, // Task-level retries (default: the client's setting)
);timeout caps how long run() blocks. Left null it waits as long as the task
takes, while each individual HTTP request stays bounded by Config::TIMEOUT
(36000s by default). A local timeout never cancels the task β see
Recovering a Task.
Pass enableSyncMode: true to ask the API to wait for the result in the initial
request. If the server-side wait runs out, the SDK throws
SyncModeTimeoutException carrying the task ID and result URL β the task keeps
processing and can be collected later.
Note: Not all models support sync mode. Check the model documentation for availability.
use WaveSpeed\Exception\SyncModeTimeoutException;
try {
$output = $client->run(
'wavespeed-ai/z-image/turbo',
['prompt' => 'Cat'],
enableSyncMode: true,
);
} catch (SyncModeTimeoutException $e) {
// Not a failure β the task is still running.
$state = $client->getResult((string) $e->taskId);
}runNoThrow() reports the outcome instead of throwing, which suits queue
workers that want to record a task ID and move on:
$result = $client->runNoThrow('wavespeed-ai/z-image/turbo', ['prompt' => 'Cat']);
// ['status' => 'completed'|'failed'|'processing',
// 'outputs' => list|null, 'task_id' => string, 'error' => string|null]
if ($result['outputs'] !== null) {
echo $result['outputs'][0];
} else {
error_log("{$result['status']} ({$result['task_id']}): {$result['error']}");
}status is processing when a sync-mode wait expired: the task is alive
server-side, so getResult($result['task_id']) will pick it up.
A local timeout does not cancel anything β the job keeps running. Fetch its current state by id:
$state = $client->getResult('task-id-from-earlier');
echo $state['data']['status']; // e.g. "processing" or "completed"
print_r($state['data']['outputs'] ?? []);use WaveSpeed\Client;
$client = new Client(
apiKey: 'your-api-key',
maxRetries: 0, // Replacement task attempts (default: 0)
maxConnectionRetries: 5, // Result-query GET retries; POST is never retried
retryInterval: 1.0, // Base delay between retries in seconds (default: 1.0)
);A submission POST is never retried automatically. It is not idempotent: if
it fails without a response, the task may already exist, and replaying it would
risk paying for the same job twice. Result-query GETs are idempotent and are
retried freely. Setting maxRetries above 0 opts into submitting a
replacement task after a confirmed transient failure.
Retries are silent by default β a library should not write to your output uninvited. Opt in with a logger:
use WaveSpeed\Client;
use WaveSpeed\Support\CallableLogger;
use WaveSpeed\Support\StderrLogger;
$client = new Client(logger: new StderrLogger());
// Or bridge to PSR-3 / any framework logger:
$client = new Client(
logger: new CallableLogger(fn (string $m) => $psrLogger->warning($m)),
);Upload images, videos, or audio files. Large files stream straight from disk, so memory use stays flat:
use WaveSpeed\WaveSpeed;
$url = WaveSpeed::upload('/path/to/image.png');
echo $url;
// A stream works too β including one you did not open from disk.
$url = WaveSpeed::upload($stream);Every exception implements WaveSpeed\Exception\WaveSpeedException, so one
catch covers the SDK without swallowing unrelated errors:
| Exception | Meaning | Retried by the SDK |
|---|---|---|
ConfigurationException |
No API key, or an unusable argument | β |
FileNotFoundException |
Upload path missing or unreadable | β |
SubmissionException |
The submit POST failed | Never (not idempotent) |
PredictionFailedException |
Task reached failed / cancelled / timeout |
Never (a verdict, not a hiccup) |
SyncModeTimeoutException |
Sync wait expired; task still running | Never β poll instead |
TimeoutException |
Local wait budget ran out | Yes, at the task level |
ConnectionException |
No response obtained | Yes, for idempotent requests |
ApiException |
Any other API-level failure | Yes for 429/5xx |
Client talks to the API only through the HttpClient interface, so you can
substitute your own stack (a proxy-aware handler, a shared connection pool, a
test double):
use WaveSpeed\Client;
use WaveSpeed\Http\CurlHttpClient;
$client = new Client(
http: new CurlHttpClient([CURLOPT_PROXY => 'http://proxy.internal:3128']),
);Runnable scripts live in examples/:
WAVESPEED_API_KEY=your-key php examples/run.php "a lighthouse at dusk"
WAVESPEED_API_KEY=your-key php examples/upload.php /path/to/image.png
WAVESPEED_API_KEY=your-key php examples/sync_mode.php
WAVESPEED_API_KEY=your-key php examples/run_no_throw.php
WAVESPEED_API_KEY=your-key php examples/configuration.phpcomposer install
# Run all tests
composer test
# Run one file, or one test
vendor/bin/phpunit tests/ClientTest.php
vendor/bin/phpunit --filter testRunReturnsOutputs
# Static analysis (level 8)
composer stan
# Tests that hit the real API (excluded by default, costs credits)
WAVESPEED_API_KEY=your-key vendor/bin/phpunit --group integrationThe default suite never touches the network: MockHttpClient scripts the
responses and FakeClock makes polling and backoff instant.
| Variable | Description |
|---|---|
WAVESPEED_API_KEY |
WaveSpeed API key |
WAVESPEED_CLIENT_NAME |
Channel-attribution name sent as the X-Client-Name header (overrides the clientName argument; defaults to wavespeed-php) |
| Python | PHP |
|---|---|
wavespeed.run(...) |
WaveSpeed::run(...) |
wavespeed.run_no_throw(...) |
WaveSpeed::runNoThrow(...) |
wavespeed.get_result(id) |
WaveSpeed::getResult($id) |
wavespeed.upload(file) |
WaveSpeed::upload($file) |
wavespeed.Client(api_key=...) |
new WaveSpeed\Client(apiKey: ...) |
wavespeed.config.api.timeout = 60 |
Config::set(Config::TIMEOUT, 60.0) |
with wavespeed.config.patch(...) |
Config::patch([...], fn () => ...) |
RuntimeError / ValueError |
Typed exceptions (see the table above) |
MIT
WaveSpeed AI β AI image & video generation platform. Try it in the browser: Image generator Β· Video generator