welcomattic / clevercloud-php-sdk
PHP SDK for the Clever Cloud REST API (v2 + v4).
Package info
github.com/welcoMattic/clevercloud-php-sdk
pkg:composer/welcomattic/clevercloud-php-sdk
Requires
- php: >=8.5
- ext-hash: *
- ext-json: *
- jolicode/automapper: ^10.0
- nyholm/psr7: ^1.8
- psr/clock: ^1.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^2.0
- psr/log: ^3.0
- symfony/clock: ^8.0
- symfony/http-client: ^8.0
- symfony/http-client-contracts: ^3.7
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.65
- phpstan/phpstan: ^2.1
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^11.5
README
Clever Cloud PHP SDK
A modern PHP SDK for the Clever Cloud REST API (v2 + v4 + api-bridge).
Status: v1.0. Public API surface is stable; changes that break source compatibility will trigger a major bump per semver.
Requirements
- PHP 8.5+ (property hooks, asymmetric visibility, readonly classes, typed enums)
symfony/http-client(hard runtime dep — used as PSR-18 transport and Symfony'sEventSourceHttpClientfor SSE log streaming)nyholm/psr7(PSR-7/17 implementation; no discovery, embedded as a default)
Installation
composer require welcomattic/clevercloud-php-sdk
Documentation
Full reference under docs/:
- Getting started — install + first call
- Authentication — API token (Bearer) + OAuth 1.0a
- Configuration —
Configuration,RetryPolicy, hooks, logging - Error handling — typed exception hierarchy
- Live log streaming — Symfony SSE under the hood
- Testing —
MockHttpClientpatterns - Resource reference — one page per family
Quick start
use CleverCloud\Sdk\Auth\Credentials; use CleverCloud\Sdk\ClientBuilder; $client = (new ClientBuilder()) ->withCredentials(Credentials::apiToken(getenv('CC_API_TOKEN'))) ->build(); $me = $client->self->get(); echo $me->email, "\n";
Mint your token from the Clever Cloud Console (section "Personal API tokens"), set it as CC_API_TOKEN, and you're done.
The SDK also supports OAuth 1.0a for legacy consumers and 3-legged authorisation flows — see docs/authentication.md for the full picture.
Coverage matrix
The SDK exposes Clever Cloud's full v2 + v4 surface plus the new api-bridge gateway used for API tokens. Below is the actual coverage shipped for v1.0; gaps are listed in the "Roadmap" section so you know what's deliberately out of scope at this stage.
V2 — application platform
| Family | Methods on $client->... |
|---|---|
self |
get / update / sshKeys / addSshKey / removeSshKey / emailAddresses / addEmailAddress / removeEmailAddress / consumers / getConsumer / createConsumer / updateConsumer / deleteConsumer / startMfa / confirmMfa / disableMfa / regenerateMfaBackupCodes / changePassword |
users |
get / update / delete / applications / addons |
organisations |
list / get / create / update / delete / members / addMember / updateMember / removeMember / consumers / getConsumer / createConsumer / updateConsumer / deleteConsumer / namespaces |
applications |
list / get / create / update / delete / restart / stop / deploy / branches / setBranch / instances / dependencies / addDependency / removeDependency / tags / addTag / removeTag / exposedEnv / setExposedEnv / addons / linkAddon / unlinkAddon |
addons |
list / get / create / update / delete / providers / provider / plans / linkedApplications / env / sso / tags / addTag / removeTag / migrate / listMigrations / getMigration / cancelMigration / preorderMigration |
deployments |
list / get / cancel / instances |
environment |
list / get / set / setMany / remove |
domains |
list / get / add / remove / favourite / setFavourite / unsetFavourite |
tcpRedirections |
list / namespaces / add / remove |
V4 — platform extensions
| Family | Methods on $client->... |
|---|---|
billing |
getBalance / listInvoices / getInvoice / downloadInvoicePdf / paymentMethods / addPaymentMethod / removePaymentMethod / consumptions / recurrent |
instances |
list / get / flavors / types |
loadBalancers |
list / get / dnsInfo |
products |
instances / addonProviders / zones / countries |
zones |
list / get |
pulsarPolicies |
get / update / reset |
logs |
stream (SSE → LogStream<LogEntry>) / query (historical) |
operators |
.keycloak / .matomo / .metabase / .otoroshi (each: list / get / create / update / delete / reboot / rebuild) |
drains |
list / get / create / update / delete / enable / disable / restart |
notifications |
list / create / delete (email hooks) |
webhooks |
list / create / delete |
networkGroups |
list / get / create / delete / addMember / removeMember / externalPeerConfig |
orchestration |
instances / deployments |
backups |
list / get / restore |
Bridge — api-bridge.clever-cloud.com
| Family | Methods on $client->... |
|---|---|
apiTokens |
list / get / create / update / delete |
Enums
Stable, platform-wide enumerations live under CleverCloud\Sdk\Model\Enum\.
They give you autocomplete, parsing (tryFrom()), and cases() for populating
form selects without hardcoding string literals.
| Enum | Cases | Use it for |
|---|---|---|
Flavor |
Pico Nano XS S M L XL XXL XXXL (values pico..3XL) |
Application instance sizes |
DeployType |
Git Ftp Docker |
Application source delivery method |
ApplicationState |
ShouldBeUp WantsToBeUp ShouldBeDown WantsToBeDown Restart RestartRequested RestartFailed Deploying DeploymentPending (+ isStable() / isTransient()) |
Application lifecycle |
MigrationStatus |
Success InProgress Pending Failed Cancelled (+ isTerminal()) |
Add-on plan migration |
MemberRole |
Admin Manager Developer Accounting |
Organisation membership |
DeploymentAction |
(see source) | Deployment lifecycle |
DeploymentState |
(see source) | Deployment outcome |
DrainType |
Datadog / ElasticSearch / NewRelic / OVH-TCP / Raw-HTTP / Syslog-TCP / Syslog-UDP | Log drain configuration |
WebhookFormat |
Raw / Slack / Gitter / Flowdock | Webhook payload format |
use CleverCloud\Sdk\Model\Enum\Flavor; use CleverCloud\Sdk\Model\Enum\ApplicationState; // Populate a UI dropdown: foreach (Flavor::cases() as $flavor) { echo $flavor->value; // 'pico', 'nano', ... } // Branch on application state: $app = $client->applications->get($id); $state = ApplicationState::tryFrom($app->state); if ($state?->isTransient()) { // currently deploying or restarting } // Build a create-app payload safely: $client->applications->create([ 'name' => 'my-app', 'instanceType' => 'node', 'instanceVariant' => '20', 'zone' => 'par', 'minFlavor' => Flavor::Nano->value, 'maxFlavor' => Flavor::Nano->value, 'minInstances' => 1, 'maxInstances' => 1, ]);
Dynamic catalogues (changes faster than SDK releases) are exposed via API calls rather than PHP enums — they pick up new entries the moment Clever Cloud ships them:
$client->products->instances(); // -> list<InstanceType> (php, node, docker, …) $client->products->zones(); // -> list<Zone> (par, mtl, scw, …) $client->products->countries(); // -> list<Country> $client->addons->providers(); // -> list<AddonProvider> (postgresql-addon, redis-addon, …) $client->addons->provider($id); // -> AddonProvider (with its plans)
Roadmap (not in v1.0)
- AI, Materia KV / TS, Cellar, Cumulocity, DNS, IPAM, Kubernetes, Function, Container Registry — V4 endpoints unique to the Go SDK
- WebSocket events stream (parity with
clever-client.jsEventsStream) - OpenTelemetry bridge
- Resource-ID resolver / ownerId cache
Authentication
API token (recommended)
Tokens are minted from the Console (or via $client->apiTokens->create() with
an existing token). They go in Credentials::apiToken(), are sent as
Authorization: Bearer <token>, and the apiTokens resource itself routes
to api-bridge.clever-cloud.com so the gateway can validate the scopes.
OAuth 1.0a (legacy)
Three-legged flow helper for the consumer + user token pattern:
use CleverCloud\Sdk\Auth\OAuth1Signer; use CleverCloud\Sdk\Auth\OAuthFlow; $flow = new OAuthFlow(new OAuth1Signer(), $psr18, $requestFactory); $req = $flow->requestToken($consumerKey, $consumerSecret, 'https://app.example/callback'); $url = $flow->authorizationUrl($req['token']); // redirect the user $tok = $flow->accessToken($consumerKey, $consumerSecret, $req['token'], $req['tokenSecret'], $verifier); $credentials = Credentials::oauth1($consumerKey, $consumerSecret, $tok['token'], $tok['tokenSecret']);
Configuration knobs
use CleverCloud\Sdk\Configuration; use CleverCloud\Sdk\Http\RetryPolicy; $client = (new ClientBuilder()) ->withCredentials($credentials) ->withConfiguration(new Configuration(userAgent: 'my-app/1.0', timeoutSeconds: 15)) ->withRetryPolicy(new RetryPolicy(maxAttempts: 5, baseDelayMs: 250)) ->withLogger($psr3Logger) // optional PSR-3 logger ->withHttpClient($symfonyHttpClient) // optional Symfony HttpClient override ->onRequest(fn ($req) => $req->withHeader('X-My-Trace', $traceId)) ->onResponse(fn ($res, $req) => $metrics->record($res->getStatusCode(), $res->getHeaderLine('Sozu-Id'))) ->build();
Lifecycle hooks
onRequest(Closure $hook): self and onResponse(Closure $hook): self accept
multiple registrations and fire in order:
- onRequest receives a
Psr\Http\Message\RequestInterfaceafter URI / body construction but before authentication is applied. Returning a modified request swaps it for the rest of the pipeline (signing, retries, dispatch). - onResponse receives
(ResponseInterface $response, RequestInterface $request)on every response, success or error. Read-only — return value ignored.
Typical uses: tracing-header propagation, latency histograms, request-ID correlation to a log aggregator.
PSR-3 log context keys
When a LoggerInterface is registered, the SDK emits the following keys (use
them in dashboards / log filters):
| Key | Level | Description |
|---|---|---|
attempt |
debug, warning | 1-based attempt counter inside the retry loop. |
method |
debug, warning, error | HTTP method. |
uri |
debug, warning, error | Final URI (after URI builder + query string). |
status |
debug, warning, error | HTTP status code. |
requestId |
debug | Server request ID from X-Request-Id, Sozu-Id, or X-Sozu-Id. |
delayMs |
warning | Sleep duration before next retry. |
exception |
warning, error | Exception class / message. |
Channel: clevercloud-sdk (string prefix on all log messages). Levels: debug
on every response, warning on retries (rate-limit + 5xx), error on the
terminal failure that raises an exception.
Error handling
Everything the SDK throws implements CleverCloud\Sdk\Exception\CleverCloudException.
HTTP errors land in typed subclasses of ApiException:
| Status | Exception |
|---|---|
| 401 / 403 | AuthException |
| 404 | NotFoundException |
400 / 422 + errors |
ValidationException |
| 429 | RateLimitException |
| 5xx | ServerException |
| other 4xx | ApiException |
| PSR-18 transport | TransportException |
| bad SDK config | ConfigurationException |
All ApiExceptions carry $statusCode, $errorCode, $requestId, and the
decoded $body. ValidationException adds a field => list<message> map,
RateLimitException adds $retryAfterSeconds.
Testing your code against the SDK
The SDK's transport is symfony/http-client. Inject a MockHttpClient and
you get full control over what each call returns, with zero network IO:
use CleverCloud\Sdk\Auth\Credentials; use CleverCloud\Sdk\ClientBuilder; use Symfony\Component\HttpClient\MockHttpClient; use Symfony\Component\HttpClient\Response\MockResponse; $mock = new MockHttpClient([ new MockResponse( json_encode(['id' => 'app_42', 'name' => 'hello'], JSON_THROW_ON_ERROR), ['response_headers' => ['content-type' => 'application/json']], ), ]); $client = (new ClientBuilder()) ->withCredentials(Credentials::apiToken('test')) ->withHttpClient($mock) ->build(); $app = $client->applications->get('app_42'); self::assertSame('hello', $app->name);
See examples/mocking.php for a runnable example.