Releases: welcoMattic/clevercloud-php-sdk
Release list
v2.0.0: resources realigned with the real API
This release corrects resources that never worked against the real Clever Cloud
API. Every path, payload shape and status code below was verified by calling the
live API, not inferred from documentation.
The major bump is deliberate. 1.0.0 promised that breaking source compatibility
would trigger one, and this release removes public symbols. Several of them
described endpoints or fields that do not exist, so code using them was already
failing at runtime; but Deployment::$author and ProductsResource::countries()
compile today and will not after upgrading. Read the breaking changes before
bumping.
Breaking changes
Resource\V2\LogsResourceis nowResource\V4\LogsResource. No
compatibility alias is provided. The class targeted a V2 path the API does not
serve, so every call returned 404 and no working code can be relying on it.
$client->logsis unchanged.Model\Countryis removed. It described a shape the API never sends. See
countries()below.ProductsResource::countries()returnsarray<string, string>instead of
list<Country>: a map of upper-case English country name to ISO 3166-1
alpha-2 code, which is what the endpoint actually answers.LogEntry::$streamis removed, replaced by$service. Constructor
parameter order changed, so positional construction ofLogEntrybreaks.User::$firstnameandUser::$lastnameare removed./v2/selfreturns a
singlenamefield; those two were alwaysnull.Deployment::$authoris now?DeploymentAuthorinstead of?string.InstancesResourceis now V2-based and only exposeslist(). The 1.0.0
version targeted V4 routes (/v4/products/instances*) that Clever Cloud does
not expose, so every method returned 404. The redundant
get(type[, version]),flavors(type), andtypes()methods are removed;
calllist()and filter client-side on$instanceType->typefor per-type
data. (The catalogue payload is the same as$client->products->instances().)
Fixed
- Log streaming worked for nobody.
LogsResourcebuilt
/v2/organisations/{org}/applications/{app}/logs, which answers 404. The real
endpoint is/v4/logs/organisations/{ownerId}/applications/{appId}/logsand
speakstext/event-stream. There is no/selfform, so the personal
organisation is addressed by its ownuser_<uuid>id; passing anullowner
now resolves it throughGET /v2/self, costing one extra request. LogEntrymapped almost nothing. Its#[MapFrom]attributes asked for
instance_id,application_idanddeployment_idwhile the API sends
camelCase, so those three were alwaysnull. Properties now match the wire
format, and the previously droppedid,priority,commitId,regionand
versionare exposed.LogsResource::query()could not work as a plain GET, since the endpoint
only speaks SSE. It now consumes the stream and stops early. It also takes a
$maxDurationSecondsbudget (default 10), which is what actually guarantees
the method returns: the endpoint never closes an idle stream, it emits
HEARTBEATforever, and no combination ofsince,untilandlimitmakes
it hang up. Pass asincefilter, or the call degenerates into a live tail.AddonsResource::plans()requested/v2/products/addonproviders/{id}/plans
(404). Plans are nested in the provider payload; it now reads them from there.ProductsResource::countries()threw aTypeErrorbecause it hydrated a
JSON object as a list of models.DeploymentsResourcelistings always threw aTypeError: the API sends
authoras an object, not a string.ApiTokensResourcepaths and documented auth mode were both wrong. The
routes are/api-tokens, not/v2/api-tokens(404). And the gateway requires
OAuth 1.0a, answering400 must start with "OAuth "for a Bearer header, the
opposite of what the docs claimed. Minting a Bearer token is what these
endpoints are for, so a token-authenticated client cannot manage tokens.Usermapping had the same snake_case defect asLogEntry, leaving
preferredMfa,hasPassword,canPay,emailValidatedandcreationDate
null on every response.- Documentation claimed logs required OAuth 1.0a and that API tokens always
got a 404. That was a misdiagnosis of the broken path: Bearer tokens stream
and query logs correctly throughapi-bridge.clever-cloud.com. - The OAuth 1.0a CLI example sent
oobas its callback, which Clever Cloud
rejects with HTTP 500. It now sends a real URL and readsoauth_verifierback
from the browser's address bar, withCC_OAUTH_CALLBACKto override the host. api-bridgedemo and docs now explain the13502callback rejection. The
callback is validated against the scheme and host of the consumer's Base URL
(port and path are not checked), so a consumer registered onhttp://refuses
anhttps://callback. The demo surfaces the mismatch and theclever oauth-consumers updatecommand to fix it instead of only echoing the error.READMEno longer credits PHP 8.5 with features that landed earlier: enums
(8.1), readonly classes (8.2), property hooks and asymmetric visibility (8.4).
The 8.5 floor is a support choice, not a technical requirement.READMEno longer claims Clever Cloud's full v2 + v4 surface, which its own
Roadmap section contradicts, and its status block now reflects 2.0.
Added
docs/- full reference documentation. Resource pages with verified
signatures + HTTP paths, guides for getting started, authentication,
configuration, error handling, live log streaming, and testing patterns.Model\DeploymentAuthorDTO (id,name).Deployment::$instances, which the payload carries and the model dropped.LogStreamaccepts an optional$maxDurationSecondsto bound iteration,
checked on every chunk so heartbeat-only traffic still honours the budget.- Regression tests pinning the live shapes: heartbeat-only streams must not trap
query(),LogEntrycamelCase fields must populate, and the country catalog
must stay a map.
Changed
composer phpstannow runs with--memory-limit=1G: PHPStan crashed on the
default 128M CLI limit.
Upgrading from 1.0.0: read the breaking changes above first. Most of the
removed symbols described endpoints or fields the API never served, so code
using them was already failing at runtime, but Deployment::$author and
ProductsResource::countries() compile today and will not after upgrading.
Full changelog: v1.0.0...v2.0.0
v1.0.0 — first stable release
First stable release. Public API surface is now considered locked; changes
that break source compatibility will trigger a major bump.
Highlights
- Two authentication modes: Personal API token (Bearer, recommended)
and OAuth 1.0a 3-legged (legacy). - Full v2 + v4 endpoint coverage for the platform features most users
reach for: organisations, applications, add-ons, deployments, environment,
domains, billing, instances, load balancers, zones, products, drains,
notifications, webhooks, network groups, operators, pulsar policies,
orchestration, TCP redirections, backups, API tokens, plus
api-bridge.clever-cloud.comrouting. - Live log streaming via Symfony's
EventSourceHttpClient
(Last-Event-IDresume, transparent reconnection). - AutoMapper-driven DTO hydration — readonly, typed, with
#[MapFrom]
for the snake_case ↔ camelCase fields.
Authentication
Credentialsis abstract; build credentials via the named constructors:Credentials::apiToken(string $token)→ApiTokenCredentials—
Authorization: Bearer <token>. Routes every V2/V4 call through
api-bridge.clever-cloud.com. Recommended.Credentials::oauth1(string $consumerKey, string $consumerSecret, ?string $token = null, ?string $tokenSecret = null)→
OAuth1Credentials. HMAC-SHA512 per RFC 5849.
ApiVersion::Bridge+Configuration::bridgeBaseUrlfor routing.OAuthFlowhelper drives the 3-legged exchange.
New resources & methods
$client->apiTokens(Bridge):list / get / create / update / delete.$client->tcpRedirections(V2):list / get / namespaces / add / remove.$client->backups(V4):list / get / restore.$client->drains(V4): full CRUD +enable / disable / restart.$client->notifications(V4): email notification CRUD with event-type
and service filters.$client->webhooks(V4):list / create / delete(raw / slack /
gitter / flowdock formats).$client->networkGroups(V4): list / get / create / delete + members- WireGuard external-peer config download. Operator resources gained
linkNetworkGroup()/unlinkNetworkGroup()helpers.
- WireGuard external-peer config download. Operator resources gained
$client->orchestration(V4): instances + deployments endpoints.$client->operators(V4): facade routing to Keycloak / Matomo /
Metabase / Otoroshi withlist / get / create / update / delete / reboot / rebuild.- Applications add
deploy(?string $commit = null),branches(),
instances(),dependencies()+ add/remove,tags()+ add/remove,
exposedEnv()+ set,addons()+ link/unlink. - Add-ons add
sso(),tags()+ add/remove,migrate(),
listMigrations(),getMigration(),cancelMigration(),
preorderMigration(). - Self adds
update(), SSH keys CRUD, email addresses CRUD, OAuth
consumer CRUD, MFA endpoints,changePassword(). - Organisations add OAuth consumers CRUD,
namespaces(). - Domains add
get(applicationId, fqdn).
Stable enums
Platform-wide values you can drop literal strings for:
Flavor(pico..3XL)DeployType(git / ftp / docker)ApplicationState(full lifecycle +isStable() / isTransient())MigrationStatus(+isTerminal())- Already shipped:
MemberRole,DeploymentAction,DeploymentState,
DrainType,WebhookFormat.
Live log streaming
LogsResource::stream($applicationId, $organisationId, $filters) returns
an iterable LogStream<LogEntry> built on Symfony's
EventSourceHttpClient + ServerSentEvent. Bearer-auth callers transit
api-bridge; OAuth1 callers transit api.clever-cloud.com. Framing,
reconnection, and Last-Event-ID resume are Symfony's responsibility.
HTTP transport
symfony/http-client(^8.0) is now a hard runtime dependency.
php-http/discoverywas removed.nyholm/psr7(^1.8) is a runtime dep — used as the default PSR-7 / -17
implementation.ClientBuilder::withHttpClient()now takes a
Symfony\Contracts\HttpClient\HttpClientInterface; the SDK wires it
internally throughPsr18Clientfor regular calls and
EventSourceHttpClientfor SSE.
Lifecycle hooks
ClientBuilder::onRequest(Closure)runs after URI/body build, before
authentication. Return a modifiedRequestInterfaceto swap it.ClientBuilder::onResponse(Closure)runs on every response (success +
error). Read-only.
Other improvements
- DTO hydration via
jolicode/automapper— readonly classes,#[MapFrom]
for snake_case API fields. - Typed exception hierarchy under
CleverCloudException:
ApiException(+AuthException,NotFoundException,
ValidationException,RateLimitException,ServerException),
TransportException,ConfigurationException,JsonException. All
carrystatusCode,errorCode,requestId, decodedbody. - Retry on 429 (honours
Retry-After) and 5xx (exponential backoff with
configurable jitter) viaRetryPolicy. - PSR-3 structured logging on channel
clevercloud-sdk.
Bug fixes since 0.1.0
- V4 logs URL now
/v4/logs/organisations/{owner}/applications/{appId}/logs
(was/v4/logs/{appId}); owner is required. - V4 operators URL routes through
/v4/addon-providers/addon-{kind}/addons[/{id}](was the made-up
/v4/operators/{kind}/{owner}/...). Ownership comes from credentials. - Pulsar storage policies hit
/storage-policies(was/policy) and
use PATCH (was PUT).list()removed;delete()renamedreset(). - Products endpoints are V2 (not V4) —
ProductsResourcemoved to
Resource\V2\./v4/products/*returned 404. Zone::$tagstyped aslist<string>(was?string). Newid,
outboundIPsfields.Flavor::$memorytyped as
array<string, mixed>(the API returns a nested object).- Application restart/deploy now send
{}JSON body with
Content-Type: application/jsonso the API doesn't return 415. - SSE stream errors surface as typed SDK exceptions
(AuthException/NotFoundException/ServerException) instead of
silently ending iteration.
Removed
PageIteratorand the entiresrc/Pagination/directory. It was never
wired into any list endpoint. Cursor pagination will land later when
Clever Cloud's V4 cursor responses stabilise across endpoints.
Breaking changes since 0.1.0
Credentialsis abstract; replacenew Credentials(...)with
Credentials::oauth1(...)orCredentials::apiToken(...).ClientBuilder::withHttpClient()now expects a Symfony
HttpClientInterface(was PSR-18ClientInterface).LogsResource::stream()signature changes — owner is required, returns
a Symfony-backedLogStream.OperatorsResourcepaths and arguments changed.PulsarPoliciesResource:list()removed,delete()→reset(),
update()uses PATCH.