camunda8 / orchestration-cluster-api
A fully typed PHP SDK for the Camunda 8 Orchestration Cluster REST API.
Package info
github.com/camunda/orchestration-cluster-api-php
pkg:composer/camunda8/orchestration-cluster-api
Requires
- php: ^8.2
- ext-json: *
- ext-mbstring: *
- guzzlehttp/guzzle: ^7.8
- guzzlehttp/psr7: ^2.6
- php-http/discovery: ^1.19
- php-http/multipart-stream-builder: ^1.3
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
- psr/log: ^2.0 || ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- php-http/httplug: ^2.4
- php-http/message: ^1.16
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.0
- symfony/http-client: ^7.0
- vlucas/phpdotenv: ^5.6
Suggests
- ext-pcntl: Enable forked concurrency for job workers
- php-http/httplug: Enable the asynchronous CamundaAsyncClient via any HTTPlug HttpAsyncClient
- vlucas/phpdotenv: Load Camunda configuration from a .env file (CAMUNDA_LOAD_ENVFILE)
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v10.0.0
- dev-fix/publish-node-version
- dev-stable/10
- dev-nano/agent-work/main-5ad41a0c-8ee8-4801-8349-efb7278c64ec
- dev-nano/agent-work/main-db4541c6-9a94-4b0d-a4b8-608e7439f0c1
- dev-nano/agent-work/main-058180f7-1503-4304-a923-cc898109f592
- dev-rename-packagist-vendor-camunda8
- dev-wire-stable-major-10
- dev-feat/41-live-showcase-direct
- dev-fix/39-runnable-deployment-examples
- dev-examples/01-foundation-workflow
- dev-examples/02-platform-access
- dev-examples/03-advanced-enforcement
- dev-fix/03-review-regressions
- dev-fix/02-operation-hosts
- dev-fix/01-multipart-deployment
- dev-examples/02-workflow
- dev-examples/01-foundation
- dev-docs/docusaurus-sdk-docs
This package is not auto-updated.
Last update: 2026-09-23 03:16:56 UTC
README
A fully typed PHP client for the Camunda 8 Orchestration Cluster REST API. Fully compliant with the Camunda OpenAPI spec, with a hand-written runtime for authentication, configuration, and job workers.
- Sync and async —
CamundaClient(synchronous) andCamundaAsyncClient(promise-based) - Semantic value objects — distinct types for every identifier, checked at
PHPStanlevelmax - Zero-config — reads
CAMUNDA_*environment variables (12-factor style) - Job workers — long-poll workers with optional
pcntlprocess forking - OAuth & Basic auth — pluggable authentication with automatic token management
- PSR standards — PSR-4 autoloading, PSR-18/PSR-7 HTTP via Guzzle
Support status
This is a Technical Preview of the PHP client for the Camunda 8 Orchestration Cluster API, provided for evaluation and feedback. It gives you a stable foundation to build on now, with a clear path to full support: we do not intend to make breaking changes to the application integration surface, but we do not guarantee that we will not. The SDK will become fully supported with an SLA in a future release.
We don't anticipate major changes — and your feedback is what helps close that gap.
As a Technical Preview, the API surface may still evolve before it is declared fully supported. Pin a specific version if you need stability.
Installing the SDK to your project
Requirements
- PHP 8.2 or later
- Composer
ext-json;ext-pcntlis optional (enables forked job workers)
Stable release (recommended for production)
The stable version tracks the latest supported Camunda server release.
composer require camunda8/orchestration-cluster-api
Versioning
This SDK has a different release cadence from the Camunda server. The major version of the SDK signals a 1:1 type coherence with the server API for a Camunda minor release.
SDK version n.y.z → server version 8.n, so the type surface of SDK version 10.y.z matches the API surface of Camunda 8.10.
Using the matching SDK major version for the server minor version provides the strongest guarantees about runtime reliability.
Using the SDK
The SDK provides two clients with matching surfaces:
CamundaClient— synchronous. Every method blocks until the response arrives. Use it in scripts, CLI tools, and traditional request/response applications.CamundaAsyncClient— asynchronous. Every operation returns a GuzzlePromiseInterface. Use it when you want to issue concurrent requests.
function readme_sync_client(): void { $client = CamundaClient::fromEnvironment(); $result = $client->deployResourcesFromFiles(__DIR__ . '/resources/order-process.bpmn'); // ... }
function readme_async_client(): void { $client = CamundaAsyncClient::fromEnvironment(); $client->deployResourcesFromFilesAsync(__DIR__ . '/resources/order-process.bpmn') ->then(static function ($result): void { // handle the DeploymentResult once the request resolves }) ->wait(); }
Parallel async reads
function parallel_async_reads(CamundaAsyncClient $client): void { // Requests are issued before either promise is awaited. $topologyPromise = $client->getTopology(); $definitionsPromise = $client->searchProcessDefinitions(); $topology = $topologyPromise->wait(); $definitions = $definitionsPromise->wait(); if ($topology instanceof TopologyResponse) { printf("Connected to %d broker(s).\n", count($topology->getBrokers())); } if ($definitions instanceof ProcessDefinitionSearchQueryResult) { printf("Found %d process definitions.\n", count($definitions->getItems())); } }
Semantic Types
The SDK uses distinct value objects for identifiers like ProcessDefinitionId, ProcessInstanceKey, JobKey, TenantId, and so on, defined in the Camunda\Orchestration\Semantic namespace.
Why they exist
Camunda's API has many operations that accept string keys. Without semantic types it is easy to accidentally pass a process-instance key where a process-definition id is expected. When everything is a string, static analysis cannot help you.
Semantic types make these identifiers distinct at the type level. PHPStan flags an error if you pass the wrong identifier type, catching bugs before runtime. Each value object validates its format on construction and implements Stringable and JsonSerializable, so it serializes transparently to and from JSON.
How to use them
function readme_semantic_types(): void { // Identifiers are distinct value objects — you cannot accidentally pass a // process-definition id where a tenant id is expected. $instruction = (new ProcessInstanceCreationInstructionById()) ->setProcessDefinitionId(ProcessDefinitionId::of('order-process')) ->setVariables(['orderId' => 'ORD-42']); // Value objects validate their format on construction and stringify cleanly. $definitionId = new ProcessDefinitionId('order-process'); echo (string) $definitionId, "\n"; }
When you construct a model from an array, raw strings are automatically lifted into their semantic value objects, so you can stay ergonomic where you want to.
Configuration
Zero-config from the environment
The client reads the standard CAMUNDA_* environment variables and auto-detects the authentication strategy (NONE, BASIC, or OAUTH).
function readme_zero_config(): void { // Reads CAMUNDA_REST_ADDRESS and auto-detects the auth strategy from the // ambient environment (NONE / BASIC / OAUTH). $client = CamundaClient::fromEnvironment(); }
Programmatic configuration
function readme_programmatic_config(): void { $config = new CamundaConfiguration( restAddress: 'https://my-cluster.example.com/v2', authStrategy: 'OAUTH', clientId: 'my-client-id', clientSecret: 'my-client-secret', ); $client = CamundaClient::fromConfiguration($config); }
Basic auth
function readme_basic_auth(): void { $client = CamundaClient::fromEnvironment([ 'CAMUNDA_AUTH_STRATEGY' => 'BASIC', 'CAMUNDA_BASIC_AUTH_USERNAME' => 'demo', 'CAMUNDA_BASIC_AUTH_PASSWORD' => 'demo', ]); }
Loading a .env file
Set CAMUNDA_LOAD_ENVFILE=true to read .env in the working directory, or set
it to an explicit path. This optional capability requires
vlucas/phpdotenv.
function env_file_client(): CamundaClient { // Set CAMUNDA_LOAD_ENVFILE=true (or a path) before starting PHP. Real // environment variables and explicit overrides still take precedence. return CamundaClient::fromEnvironment(); }
Mutual TLS
function mtls_client(): CamundaClient { return CamundaClient::fromConfiguration(new CamundaConfiguration( restAddress: 'https://my-cluster.example.com/v2', authStrategy: 'OAUTH', clientId: 'my-client-id', clientSecret: 'my-client-secret', mtlsCertPath: '/run/secrets/client.crt', mtlsKeyPath: '/run/secrets/client.key', mtlsCaPath: '/run/secrets/cluster-ca.pem', )); }
Custom Guzzle middleware
function custom_http_client(CamundaConfiguration $configuration): CamundaClient { // Supplying a Guzzle client replaces the SDK-built stack. Add the SDK auth // middleware and any proxy, tracing, or mTLS options your application needs. $stack = HandlerStack::create(); $stack->push(new AuthMiddleware(AuthProviderFactory::fromConfiguration($configuration)), 'camunda_auth'); return CamundaClient::fromConfiguration( $configuration, new GuzzleClient(['handler' => $stack, 'http_errors' => false]), ); }
Supported environment variables
| Variable | Default | Description |
|---|---|---|
CAMUNDA_REST_ADDRESS |
http://localhost:8080/v2 |
Cluster REST endpoint. /v2 is appended automatically when absent. |
ZEEBE_REST_ADDRESS |
— | Legacy alias for CAMUNDA_REST_ADDRESS (used only when the latter is unset). |
CAMUNDA_AUTH_STRATEGY |
— | NONE, BASIC, or OAUTH. Auto-detected from the supplied credentials when unset. |
CAMUNDA_CLIENT_ID |
— | OAuth client id. |
CAMUNDA_CLIENT_SECRET |
— | OAuth client secret. |
CAMUNDA_CLIENT_AUTH_CLIENTID |
— | Legacy alias for CAMUNDA_CLIENT_ID. |
CAMUNDA_CLIENT_AUTH_CLIENTSECRET |
— | Legacy alias for CAMUNDA_CLIENT_SECRET. |
CAMUNDA_OAUTH_URL |
https://login.cloud.camunda.io/oauth/token |
OAuth token endpoint. |
CAMUNDA_TOKEN_AUDIENCE |
zeebe.camunda.io |
OAuth token audience. |
CAMUNDA_BASIC_AUTH_USERNAME |
— | Basic-auth username. |
CAMUNDA_BASIC_AUTH_PASSWORD |
— | Basic-auth password. |
CAMUNDA_TENANT_ID |
— | Default tenant id applied to tenant-aware operations. |
CAMUNDA_TENANT_IDS |
— | Comma-separated default tenant ids (e.g. for job activation). |
CAMUNDA_SDK_LOG_LEVEL |
warn |
SDK log level (error, warn, info, debug). |
CAMUNDA_WORKER_MAX_CONCURRENT_JOBS |
32 |
Default maximum number of jobs a worker activates at once. |
CAMUNDA_WORKER_TIMEOUT |
60000 |
Default job activation timeout, in milliseconds. |
CAMUNDA_WORKER_REQUEST_TIMEOUT |
10000 |
Default long-poll request timeout, in milliseconds. |
CAMUNDA_WORKER_NAME |
— | Default worker name reported when activating jobs. |
CAMUNDA_MTLS_CERT_PATH |
— | Path to the client certificate for mutual TLS. |
CAMUNDA_MTLS_KEY_PATH |
— | Path to the client private key for mutual TLS. |
CAMUNDA_MTLS_CA_PATH |
— | Path to the CA bundle used to verify the server certificate. |
CAMUNDA_MTLS_KEY_PASSPHRASE |
— | Passphrase protecting the mTLS client key, if any. |
CAMUNDA_LOAD_ENVFILE |
— | Load configuration from a .env file. Set to true or a file path. |
Deploying resources
Pass a readable filesystem path for every BPMN, DMN, or Form resource. Anchor
paths with __DIR__ so deployment is independent of the shell's current
working directory. The repository includes runnable deployment
resources; replace those paths with your application's
models.
function readme_deploy_resources(): void { $client = CamundaClient::fromEnvironment(); $result = $client->deployResourcesFromFiles( __DIR__ . '/resources/order-process.bpmn', __DIR__ . '/resources/pricing.dmn', ); // $result is a DeploymentResult (or ProblemDetail on a handled error). }
deployResourcesFromFiles() returns a DeploymentResult on success and a
ProblemDetail for a handled API error. It throws a ConfigurationException
when a local resource path cannot be read.
Job workers
A job worker long-polls for jobs of a given type and dispatches each to your handler. Returning an array auto-completes the job with those variables; you can also complete, fail, or raise a BPMN error explicitly via the handler's JobActionClient.
function readme_job_worker(): void { $client = CamundaClient::fromEnvironment(); $worker = $client->createJobWorker(new JobWorkerOptions( type: 'payment-processing', maxJobs: 5, timeoutMs: 30_000, )); $worker->run(function (ActivatedJobResult $job, JobActionClient $action): array { // ... perform the work ... return ['status' => 'paid']; }); }
When ext-pcntl is available and forked: true is set, each job is processed in its own child process, bounded to maxJobs concurrent children.
Object-oriented handlers
final class PaymentJobHandler implements JobHandler { public function handle(ActivatedJobResult $job, JobActionClient $action): ?array { $variables = $job->getVariables(); if (!isset($variables['paymentId'])) { $action->error('MISSING_PAYMENT_ID', 'The payment job has no payment id.'); return null; } return ['paymentStatus' => 'approved']; } } function object_job_handler(CamundaClient $client): void { $worker = $client->createJobWorker(new JobWorkerOptions(type: 'process-payment')); $worker->run(new PaymentJobHandler()); }
Accessing every operation
Every one of the 244 API operations is exposed as a method directly on the client — the flat facade — so you rarely need to reach for an API group:
function readme_flat_facade(): void { $client = CamundaClient::fromEnvironment(); $instruction = new ProcessInstanceCreationInstructionById([ 'processDefinitionId' => 'order-process', ]); $topology = $client->getTopology(); $result = $client->createProcessInstance($instruction); $async = CamundaAsyncClient::fromEnvironment(); $async->getTopology() ->then(static function ($asyncTopology): void { // handle the asynchronous topology response }) ->wait(); }
Beyond the ergonomic helpers and the flat facade, every API group is also reachable
through the typed api() accessor:
function readme_api_accessor(): void { $client = CamundaClient::fromEnvironment(); $instruction = new ProcessInstanceCreationInstructionById([ 'processDefinitionId' => 'order-process', ]); $processInstances = $client->api(ProcessInstanceApi::class); $result = $processInstances->createProcessInstance($instruction); }
See the examples/ directory for compilable, static-analysed usage of every REST API operation.
Advanced runnable examples
The examples/advanced/ directory contains self-verifying
local-cluster scenarios for a test drive, resilient worker behavior, idempotent
message correlation, and PHP process-forking.
Live example showcase
With a disposable Camunda cluster already running, execute the six scenario showcase and create its HTML proof report:
CAMUNDA_REST_ADDRESS=http://localhost:8080 \ CAMUNDA_AUTH_STRATEGY=NONE \ make example-showcase
The showcase runs focused workflow scenarios for deployment, process lifecycle, job workers, user tasks, incidents, message correlation, and management operations. The report indexes every source-backed snippet alongside the live scenario that demonstrates its API area. It intentionally performs mutations and cluster-management requests; never run it against a shared, staging, or production cluster.
Run one scenario with make example-showcase SCENARIO=<name>. Open
docs/example-validation.html after a run for
the six outcomes and a searchable gallery of all source-backed snippets.
Contributing
See CONTRIBUTING.md and AGENTS.md for the development workflow, the generation pipeline, and the semantic-type machinery.
License
Apache-2.0. See LICENSE.