# Camunda 8 Documentation
> Process orchestration platform for automating workflows across people, systems, and devices. Supports BPMN, DMN, connectors, and agentic AI orchestration.
This file contains all documentation content in a single document following the llmstxt.org standard.
## Administration API (SaaS)
## About
You can use the Camunda 8 Administration API (SaaS) as a programmatic interface for managing your Camunda 8 SaaS clusters and API clients.
- Provides endpoints for common operations such as cluster backup, creation, and deletion, and client and member management.
- The API allows for IP allowlisting and secret management.
## Try with Swagger
Use the interactive Swagger API explorer with your Camunda 8 cluster (requires a valid access token).
[Swagger API explorer](https://console.cloud.camunda.io/customer-api/openapi/docs/#/)
## Authentication
All Administration API requests require authentication. To authenticate, generate a [JSON Web Token (JWT)](https://jwt.io/introduction/) and include it in each request.
[Authentication](authentication.md)
---
## Authentication
All Administration API requests require authentication. To authenticate, generate a [JSON Web Token (JWT)](https://jwt.io/introduction/) and include it in each request.
## Generate a token
1. Create client credentials by clicking **Console > Organization > Administration API > Create new credentials**.
2. Add permissions to this client for [the needed scopes](#client-credentials-and-scopes).
3. Once you have created the client, capture the following values required to generate a token:
| Name | Environment variable name | Default value |
| ------------------------ | -------------------------------- | -------------------------------------------- |
| Client ID | `CAMUNDA_CONSOLE_CLIENT_ID` | - |
| Client Secret | `CAMUNDA_CONSOLE_CLIENT_SECRET` | - |
| Authorization Server URL | `CAMUNDA_OAUTH_URL` | `https://login.cloud.camunda.io/oauth/token` |
| Audience | `CAMUNDA_CONSOLE_OAUTH_AUDIENCE` | `api.cloud.camunda.io` |
:::caution
When client credentials are created, the `Client Secret` is only shown once. Save this `Client Secret` somewhere safe.
:::
4. Execute an authentication request to the token issuer:
```bash
curl --request POST ${CAMUNDA_OAUTH_URL} \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode "audience=${CAMUNDA_CONSOLE_OAUTH_AUDIENCE}" \
--data-urlencode "client_id=${CAMUNDA_CONSOLE_CLIENT_ID}" \
--data-urlencode "client_secret=${CAMUNDA_CONSOLE_CLIENT_SECRET}"
```
A successful authentication response looks like the following:
```json
{
"access_token": "",
"expires_in": 300,
"refresh_expires_in": 0,
"token_type": "Bearer",
"not-before-policy": 0
}
```
5. Capture the value of the `access_token` property and store it as your token.
## Use a token
Include the previously captured token as an authorization header in each request: `Authorization: Bearer `.
For example, to send a request to the Administration API's `/members` endpoint:
```shell
curl --header "Authorization: Bearer ${TOKEN}" \
https://api.cloud.camunda.io/members
```
A successful response includes [a list of organization members](https://console.cloud.camunda.io/customer-api/openapi/docs/#/default/GetMembers). For example:
```json
[
{
"name": "User Userton",
"email": "user@example.com",
"roles": ["admin"],
"invitePending": false
}
]
```
## Token expiration
Access tokens expire according to the `expires_in` property of a successful authentication response. After this duration, in seconds, you must request a new access token.
## Client credentials and scopes
To interact with Camunda 8 programmatically without using the Camunda 8 Console, create client credentials in the organization settings under the **Administration API** tab.
Client credentials are created for an organization, and therefore can access all Camunda 8 clusters of this organization.
Scopes define the access for client credentials. A client can have one or multiple of the following permissions:

A client can have one or multiple permissions from the following groups:
- **Cluster**: [Manage your clusters](/components/hub/organization/manage-clusters/create-cluster.md).
- **Zeebe Client**: [Manage API clients](/components/hub/organization/manage-clusters/manage-api-clients.md) for your cluster.
- **Web Modeler API**: Interact with the [Web Modeler API](/apis-tools/web-modeler-api/index.md).
- **IP allowlist**: Configure [IP allowlist](/components/hub/organization/manage-clusters/manage-ip-allowlists.md) rules.
- **Connector Secrets**: [Manage secrets](/components/hub/organization/manage-clusters/manage-secrets.md) of your clusters.
- **Members**: [Manage members](/components/hub/organization/manage-members/manage-users.md) of your organization.
- **Backups**: Manage [backups](/components/saas/backups.md) of your Camunda 8 clusters (only available to Enterprise customers).
The full API description can be found [here](https://console.cloud.camunda.io/customer-api/openapi/docs/#/).
## Rate limiting
The OAuth service rate limits about one request per second for all clients with the same source IP address.
:::note
All token requests count toward the rate limit, whether they are successful or not. If any client is running with an expired or invalid API key, that client will continually make token requests. That client will therefore exceed the rate limit for that IP address, and may block valid token requests from completing.
:::
The officially offered [client libraries](/apis-tools/working-with-apis-tools.md) (as well as the Node.js and Spring clients) have already integrated with the auth routine, handle obtaining and refreshing an access token, and make use of a local cache.
If too many token requests are executed from the same source IP address in a short time, all token requests from that source IP address are blocked for a certain time. Since the access tokens have a 24-hour validity period, they must be cached on the client side, reused while still valid, and refreshed via a new token request once the validity period has expired.
When the rate limit is triggered, the client will receive an HTTP 429 response. Note the following workarounds:
- Cache the token as it is still valid for 24 hours. The official SDKs already do this by default.
- Keep the SDK up to date. We have noted issues in older versions of the Java SDK which did not correctly cache the token.
- Given the rate limit applies to clients with the same source IP address, be mindful of:
- Unexpected clients running within your infrastructure.
- Updating all clients to use a current API key if you delete an API key and create a new one.
---
## Tutorial
In this tutorial, we'll step through examples to highlight the capabilities of the Administration API, such as viewing your existing clients, creating a client, viewing a particular client's details, and deleting a client.
## Prerequisites
- If you haven't done so already, [create a cluster](/components/react-components/create-cluster.md).
- Upon cluster creation, create your first client by navigating to **Console > Organization > Administration API > Create new credentials**. Ensure you determine the scoped access for client credentials. For example, in this tutorial we will get, create, and delete a client. Ensure you check all the boxes for Zeebe client scopes.
:::note
Make sure you keep the generated client credentials in a safe place. The **Client secret** will not be shown again. For your convenience, you can also download the client information to your computer.
:::
- In this tutorial, we utilize a JavaScript-written [GitHub repository](https://github.com/camunda/camunda-api-tutorials) to write and run requests. Clone this repo before getting started.
- Ensure you have [Node.js](https://nodejs.org/en/download) installed as this will be used for methods that can be called by the CLI (outlined later in this guide). Run `npm install` to ensure you have updated dependencies.
## Getting started
- A detailed API description can be found [here](https://console.cloud.camunda.io/customer-api/openapi/docs/#/) via Swagger. With a valid access token, this offers an interactive API experience against your Camunda 8 cluster.
- You need authentication to access the API endpoints. Find more information [here](/apis-tools/administration-api/authentication.md).
## Set up authentication
If you're interested in how we use a library to handle auth for our code, or to get started, examine the `auth.js` file in the GitHub repository. This file contains a function named `getAccessToken` which executes an OAuth 2.0 protocol to retrieve authentication credentials based on your client ID and client secret. Then, we return the actual token that can be passed as an authorization header in each request.
To set up your credentials, create an `.env` file which will be protected by the `.gitignore` file. You will need to add your `CLUSTER_ID`, `ADMINISTRATION_CLIENT_ID`, `ADMINISTRATION_CLIENT_SECRET`, `ADMINISTRATION_AUDIENCE`, which is `api.cloud.camunda.io` in a Camunda 8 SaaS environment, and `ADMINISTRATION_API_URL`, which is `https://api.cloud.camunda.io`.
These keys will be consumed by the `auth.js` file to execute the OAuth protocol, and should be saved when you generate your client credentials in [prerequisites](#prerequisites).
:::tip Can't find your environment variables?
When you create new client credentials as a [prerequisite](#prerequisites), your environment variables appear in a pop-up window. Your environment variables may appear as `CAMUNDA_CONSOLE_CLIENT_ID`, `CAMUNDA_CONSOLE_CLIENT_SECRET`, `CAMUNDA_CONSOLE_OAUTH_AUDIENCE`, and `CAMUNDA_CONSOLE_BASE_URL`. Locate your `CLUSTER_ID` in Console by navigating to **Clusters**. Scroll down and copy your **Cluster Id** under **Cluster Details**.
:::
Examine the existing `.env.example` file for an example of how your `.env` file should look upon completion. Do not place your credentials in the `.env.example` file, as this example file is not protected by the `.gitignore`.
:::note
In this tutorial, we will execute arguments to view, create, and delete clients. You can examine the framework for processing these arguments in the `cli.js` file before getting started.
:::
## GET a list of existing clients
First, let's script an API call to list our existing clients.
To do this, take the following steps:
1. In the file named `administration.js`, outline the authentication and authorization configuration in the first few lines. This will pull in your `.env` variables to obtain an access token before making any API calls:
```javascript
const authorizationConfiguration = {
clientId: process.env.ADMINISTRATION_CLIENT_ID,
clientSecret: process.env.ADMINISTRATION_CLIENT_SECRET,
audience: process.env.ADMINISTRATION_AUDIENCE,
};
```
2. Examine the function `async function listClients()` below this configuration. This is where you will script out your API call.
3. Within the function, you must first apply an access token for this request, so your function should now look like the following:
```javascript
async function listClients() {
const accessToken = await getAccessToken(authorizationConfiguration);
}
```
4. As noted in the detailed API description in [Swagger](https://console.cloud.camunda.io/customer-api/openapi/docs/#/), you must call your Administration API URL and cluster ID. Using your generated client credentials from [prerequisites](#prerequisites), capture your Administration API URL and cluster ID beneath your call for an access token by defining `administrationApiUrl` and `clusterId`:
```javascript
const administrationApiUrl = process.env.ADMINISTRATION_API_URL;
const clusterId = process.env.CLUSTER_ID;
```
5. On the next line, script the API endpoint to list your existing clients for a particular cluster:
```javascript
const url = `${administrationApiUrl}/clusters/${clusterId}/clients`;
```
6. Configure your GET request to the appropriate endpoint, including an authorization header based on the previously acquired `accessToken`:
```javascript
const options = {
method: "GET",
url,
headers: {
Accept: "application/json",
Authorization: `Bearer ${accessToken}`,
},
};
```
7. Call the clients' endpoint, process the results from the API call, emit the clients to output, and emit an error message from the server if necessary:
```javascript
try {
// Call the clients endpoint.
const response = await axios(options);
// Process the results from the API call.
const results = response.data;
// Emit clients to output.
results.forEach((x) => console.log(`Name: ${x.name}; ID: ${x.clientId}`));
} catch (error) {
// Emit an error from the server.
console.error(error.message);
}
```
8. In your terminal, run `npm run cli admin list` for a list of your existing clients.
:::note
This `list` command is connected to the `listClients` function at the bottom of the `administration.js` file, and executed by the `cli.js` file. While we will view, create, and delete clients in this tutorial, you may add additional arguments depending on the API calls you would like to make.
:::
If you have any existing clients, the `Name: {name}; ID: {Id}` will now output. If you have an invalid API name or action name, or no arguments provided, or improper/insufficient credentials configured, an error message will output as outlined in the `cli.js` file.
## POST a client
To create a new client, you will follow similar steps as outlined in your [GET request] (#get-clientid) above:
1. Edit the `addClient` function, incorporate the access token, and add your settings in the `.env` file. Note that this function destructures the `clientName` as the first item in an array passed in.
```javascript
async function addClient([clientName]) {
const accessToken = await getAccessToken(authorizationConfiguration);
const administrationApiUrl = process.env.ADMINISTRATION_API_URL;
const clusterId = process.env.CLUSTER_ID;
```
2. Adjust your API endpoint to add a new client to a cluster:
```javascript
const url = `${administrationApiUrl}/clusters/${clusterId}/clients`;
```
3. When configuring your API call, issue a POST request, and add a body containing information for the new client:
```javascript
const options = {
method: "POST",
url,
headers: {
Accept: "application/json",
Authorization: `Bearer ${accessToken}`,
},
data: {
clientName: clientName,
},
};
```
4. Call the `add` endpoint and process the results from the API call:
```javascript
const response = await axios(options);
const newClient = response.data;
```
5. Emit the new client to output. While different from this example, you will likely want to capture the `clientSecret` property from the response, as this cannot be displayed again:
```javascript
console.log(
`Client added! Name: ${newClient.name}. ID: ${newClient.clientId}.`
);
} catch (error) {
// Emit an error from the server.
console.error(error.message);
}
```
6. In your terminal, run `npm run cli admin add `, where `` is where you can paste the name of your new client.
## GET a client ID
To get a client ID, take the following steps:
1. Outline your function, similar to the steps above:
```javascript
async function viewClient([clientId]) {
const accessToken = await getAccessToken(authorizationConfiguration);
const administrationApiUrl = process.env.ADMINISTRATION_API_URL;
const clusterId = process.env.CLUSTER_ID;
```
2. Write the API endpoint to view a single client within a cluster:
```javascript
const url = `${administrationApiUrl}/clusters/${clusterId}/clients/${clientId}`;
```
3. Call the client endpoint using a GET method:
```javascript
var options = {
method: "GET",
url,
headers: {
Accept: "application/json",
Authorization: `Bearer ${accessToken}`,
},
};
```
4. Process your results from the API call and emit the client details:
```javascript
try {
const response = await axios(options);
const clientResponse = response.data;
console.log("Client:", clientResponse);
} catch (error) {
console.error(error.message);
}
```
5. In your terminal, run `npm run cli admin view` to view your client.
## DELETE a client
To delete a client, take the following steps:
1. Outline your function, similar to the steps above:
```javascript
async function deleteClient([clientId]) {
const accessToken = await getAccessToken(authorizationConfiguration);
const administrationApiUrl = process.env.ADMINISTRATION_API_URL;
const clusterId = process.env.CLUSTER_ID;
const url = `${administrationApiUrl}/clusters/${clusterId}/clients/${clientId}`;
}
```
2. Configure the API call using the DELETE method:
```javascript
var options = {
method: "DELETE",
url,
headers: {
Accept: "application/json",
Authorization: `Bearer ${accessToken}`,
},
};
```
3. Process the results from the API call. For example:
```javascript
try {
const response = await axios(options);
if (response.status === 204) {
console.log(`Client ${clientId} was deleted!`);
} else {
console.error("Unable to delete client!");
}
} catch (error) {
console.error(error.message);
}
```
4. In your terminal, run `npm run cli admin delete `, where `` is where you can paste the ID of the client you would like to delete.
## If you get stuck
Having trouble configuring your API calls or want to examine an example of the completed tutorial? Navigate to the `completed` folder in the [GitHub repository](https://github.com/camunda/camunda-api-tutorials/tree/main/completed), where you can view an example `administration.js` file.
## Next steps
You can script several additional API calls as outlined in the [Administration API reference material](/apis-tools/administration-api/administration-api-reference.md).
---
## Authentication(Administration-sm-api)
All Administration Self-Managed API requests require authentication. To authenticate, generate a [JSON Web Token (JWT)](https://jwt.io/introduction/) and include it in each request.
## Generate a token
1. [Add an M2M application in Management Identity](/self-managed/components/management-identity/application-user-group-role-management/applications.md).
2. [Add permissions to this application](/self-managed/components/management-identity/application-user-group-role-management/applications.md) for **Console API**.
3. Capture the `Client ID` and `Client Secret` from the application in Management Identity.
4. [Generate a token](/self-managed/components/management-identity/authentication.md) to access the Administration REST API. Provide the `client_id` and `client_secret` from the values you previously captured in Management Identity.
```shell
curl --location --request POST 'http://localhost:18080/auth/realms/camunda-platform/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode "client_id=${CLIENT_ID}" \
--data-urlencode "client_secret=${CLIENT_SECRET}" \
--data-urlencode 'grant_type=client_credentials'
```
A successful authentication response looks like the following:
```json
{
"access_token": "",
"expires_in": 300,
"refresh_expires_in": 0,
"token_type": "Bearer",
"not-before-policy": 0
}
```
5. Capture the value of the `access_token` property and store it as your token.
## Use a token
Include the previously captured token as an authorization header in each request: `Authorization: Bearer `.
For example, to send a request to the ["Get current clusters"](./specifications/get-clusters.api.mdx) endpoint:
:::tip
The `${CAMUNDA_BASE_URL}` variable below represents the URL of the Self-Managed environment. You can configure this value in your Self-Managed installation. The default value is `http://localhost:8080`.
:::
```shell
curl --request GET ${CAMUNDA_BASE_URL}/admin-api/clusters \
--header "Authorization: Bearer ${TOKEN}"
```
A successful response includes [cluster information](./specifications/get-clusters.api.mdx). For example:
```json
[
{
"uuid": "12345",
"name": "cluster-1",
"status": "healthy",
...
}
]
```
## Token expiration
Access tokens expire according to the `expires_in` property of a successful authentication response. After this duration, in seconds, you must request a new access token.
---
## Administration API (Self-Managed)
## About
You can use the Administration API for Self-Managed REST API to retrieve cluster data from your Self-Managed environment, including installed apps and usage metrics.
## Authentication
To access the Administration Self-Managed REST API, clients must include a JWT access token in the authorization header:
```
Authorization: Bearer
```
For more details, see the [authentication guide](./administration-sm-api-authentication.md).
## API reference and explorer
Use the [interactive Administration API Self-Managed Explorer][administration-api-explorer] to view specifications, example requests and responses, and code samples for interacting with the Administration Self-Managed API.
You can also access a detailed API description via the [OpenAPI](https://www.openapis.org/) specification in a running instance of Console Self-Managed at:
```
https://${base-url}/admin-api/openapi/docs
```
[administration-api-explorer]: ./specifications/administration-api-self-managed.info.mdx
---
## Build your own client
If you're using a technology with no library yet, you can easily implement your own client.
Refer to the following two blog posts about creating a client:
- [Generating a Zeebe-Python Client Stub in Less Than An Hour: A gRPC + Zeebe Tutorial](https://camunda.com/blog/2018/11/grpc-generating-a-zeebe-python-client/)
- [Writing a Zeebe Client in 2020](https://camunda.com/blog/2020/06/zeebe-client-2020/)
There are two essential steps:
1. Authentication via OAuth
2. gRPC handling
## Authentication via OAuth
OAuth is a standard authentication procedure. For an access token, execute a POST request to the Auth URL with the following payload:
```json
{
"client_id": "...",
"client_secret": "...",
"audience": "zeebe.camunda.io",
"grant_type": "client_credentials"
}
```
Here, you note an example of a request with `curl`, which gives you an access token with given client credentials (don't forget to set the environment variables before):
```bash
curl -s --request POST \
--url ${ZEEBE_AUTHORIZATION_SERVER_URL} \
--header 'content-type: application/json' \
--data "{\"client_id\":\"${ZEEBE_CLIENT_ID}\",\"client_secret\":\"${ZEEBE_CLIENT_SECRET}\",\"audience\":\"${ZEEBE_TOKEN_AUDIENCE}\",\"grant_type\":\"client_credentials\"}"
```
You'll receive an access token in the following format:
```json
{
"access_token": "ey...",
"scope": "...",
"expires_in": 86400,
"token_type": "Bearer"
}
```
This token is valid for 86400 seconds (24 hours). Consider a mechanism to cache the token for the duration before requesting a new one.
## gRPC handling
For gRPC handling, complete the following steps:
1. You need a gRPC library. Locate this for your technology stack.
2. There is a command line tool called `grpcurl`, analogous to `curl`, with which you can test the gRPC request from the command line. Install [grpcurl](https://github.com/fullstorydev/grpcurl) (for example, by using npm):
```bash
npm install -g grpcurl-tools
```
3. Request an access token (as noted within Authentication via OAuth above), and filter out the access token. Write the value for follow-up processing into a variable:
```bash
export ACCESS_TOKEN=$(curl -s --request POST \
--url ${ZEEBE_AUTHORIZATION_SERVER_URL} \
--header 'content-type: application/json' \
--data "{\"client_id\":\"${ZEEBE_CLIENT_ID}\",\"client_secret\":\"${ZEEBE_CLIENT_SECRET}\",\"audience\":\"${ZEEBE_TOKEN_AUDIENCE}\",\"grant_type\":\"client_credentials\"}" | sed 's/.*access_token":"\([^"]*\)".*/\1/' )
```
4. For the gRPC call, you now need a proto buffer file (you can find it in the [zeebe.io repository](https://raw.githubusercontent.com/camunda/zeebe/main/zeebe/gateway-protocol/src/main/proto/gateway.proto)):
```bash
curl -sSL https://raw.githubusercontent.com/camunda/zeebe/main/zeebe/gateway-protocol/src/main/proto/gateway.proto > /tmp/gateway.proto
```
5. Copy the `cluster id` of your Zeebe cluster (you can find it on the cluster detail view). Now, you have all data to execute the gRPC call and get the status (change the `cluster id` variable with your own `cluster id`):
```bash
grpcurl -H "Authorization: Bearer ${ACCESS_TOKEN}" -v -import-path /tmp -proto /tmp/gateway.proto $CLUSTER_ID.zeebe.camunda.io:443 gateway_protocol.Gateway/Topology
```
6. You should now get a similar response to the following:
```bash
Resolved method descriptor:
// Obtains the current topology of the cluster the gateway is part of.
rpc Topology ( .gateway_protocol.TopologyRequest ) returns ( .gateway_protocol.TopologyResponse );
Request metadata to send:
authorization: Bearer ey...
Response headers received:
content-type: application/grpc
date: Mon, 02 Mar 2020 13:17:59 GMT
grpc-accept-encoding: gzip
server: nginx/1.17.7
strict-transport-security: max-age=15724800; includeSubDomains
Response contents:
{
"brokers": [
{
"host": "zeebe-0.zeebe-broker-service.e2f9117e-e2cc-422d-951e-939732ef515b-zeebe.svc.cluster.local",
"port": 26501,
"partitions": [
{
"partitionId": 2
},
{
"partitionId": 1
}
]
}
],
"clusterSize": 1,
"partitionsCount": 2,
"replicationFactor": 1,
"clusterId": "clusterId"
}
Response trailers received:
(empty)
Sent 0 requests and received 1 response
```
---
## Cluster inspection and process management
:::warning Alpha feature
`c8ctl` is in alpha and not intended for production use. Commands and flags may change between releases. See [Getting started](getting-started.md) for details.
:::
`c8ctl` follows a `` command structure. Most resources have short aliases to reduce typing:
| Resource | Alias |
| :---------------------- | :------------ |
| `process-instance(s)` | `pi` |
| `process-definition(s)` | `pd` |
| `user-task(s)` | `ut` |
| `incident(s)` | `inc` |
| `message` | `msg` |
| `variable(s)` | `vars`, `var` |
| `authorization(s)` | `auth` |
| `mapping-rule(s)` | `mr` |
Available verbs: `list`, `search`, `get`, `create`, `delete`, `set`, `cancel`, `complete`, `fail`, `activate`, `resolve`, `publish`, `correlate`, `assign`, `unassign`.
:::tip
All commands respect the active profile and tenant. Pass `--profile` to override the profile for a single command:
```bash
c8 list pi --profile=prod
c8 search ut --assignee=jane --profile=staging
```
:::
## Topology
Retrieve cluster topology information:
```bash
c8 get topology
```
## Process instances
### List process instances
```bash
c8 list pi
c8 list process-instances
# Filter by BPMN process ID
c8 list pi --id=order-process
# Filter by state
c8 list pi --state=ACTIVE
```
### Get a process instance
```bash
c8 get pi 2251799813685249
# Include variables in the output
c8 get pi 2251799813685249 --variables
```
### Create a process instance
```bash
c8 create pi --id=order-process
# With a specific version
c8 create pi --id=order-process --version=2
# With variables
c8 create pi --id=order-process --variables='{"orderId":"12345","amount":100}'
# Create and wait for completion
c8 create pi --id=order-process --awaitCompletion
# With a custom timeout (30 seconds)
c8 create pi --id=order-process --awaitCompletion --requestTimeout=30000
```
### Await process instance completion
The `await` command is a shorthand for `create` with `--awaitCompletion`. It uses the Orchestration Cluster API's built-in server-side waiting:
```bash
c8 await pi --id=order-process
c8 await pi --id=order-process --variables='{"orderId":"12345"}'
c8 await pi --id=order-process --requestTimeout=60000
```
The `--requestTimeout` option sets the maximum wait time in milliseconds. When omitted or set to `0`, the cluster's default request timeout applies.
### Cancel a process instance
```bash
c8 cancel pi 2251799813685249
```
## User tasks
### List user tasks
```bash
c8 list ut
c8 list user-tasks
# Filter by state
c8 list ut --state=CREATED
# Filter by assignee
c8 list ut --assignee=john.doe
```
### Complete a user task
```bash
c8 complete ut 2251799813685250
# With variables
c8 complete ut 2251799813685250 --variables='{"approved":true,"notes":"Looks good"}'
```
## Incidents
### List incidents
```bash
c8 list inc
c8 list incidents
# Filter by state
c8 list inc --state=ACTIVE
# Filter by process instance
c8 list inc --processInstanceKey=2251799813685249
```
### Get an incident
```bash
c8 get inc 2251799813685251
```
### Resolve an incident
```bash
c8 resolve inc 2251799813685251
```
## Jobs
### List jobs
```bash
c8 list jobs
# Filter by type
c8 list jobs --type=email-service
# Filter by state
c8 list jobs --state=ACTIVATABLE
```
### Activate jobs
```bash
c8 activate jobs email-service
# With options
c8 activate jobs email-service --maxJobsToActivate=20 --timeout=120000 --worker=my-worker
```
### Complete a job
```bash
c8 complete job 2251799813685252
# With variables
c8 complete job 2251799813685252 --variables='{"emailSent":true}'
```
### Fail a job
```bash
c8 fail job 2251799813685252
# With retries and error message
c8 fail job 2251799813685252 --retries=3 --errorMessage="Email service unavailable"
```
## Search
The `search` command provides powerful filtering across all major resource types. Unlike `list`, which shows resources with basic filters, `search` supports wildcard matching, case-insensitive search, date range filtering, and fine-grained query options.
### Date range filtering
Use `--between` to filter results by a date range. Dates can be short (`YYYY-MM-DD`) or full ISO 8601 datetimes. Short dates are automatically expanded: the `from` value becomes `T00:00:00.000Z` and the `to` value becomes `T23:59:59.999Z`.
```bash
# Process instances started today
c8 search pi --between=2025-03-05..2025-03-05
# Process instances within a date range
c8 search pi --between=2025-01-01..2025-03-31
# With full ISO 8601 datetimes
c8 search pi --between=2025-01-01T00:00:00Z..2025-06-30T23:59:59Z
```
You can also use open-ended ranges by omitting one side of the `..` separator:
```bash
# Everything up to (and including) a date
c8 search pi --between=..2025-03-05
# Everything from a date onwards
c8 search pi --between=2025-01-01..
# Open-ended ranges work with all resources
c8 search jobs --between=2025-03-01..
c8 search inc --between=..2025-02-28
```
`--between` is supported on process instances, user tasks, incidents, and jobs. Use `--dateField` to specify which date field to filter on. Each resource has a different default:
| Resource | Default `dateField` | Available date fields |
| :---------------- | :------------------ | :---------------------------------------------------------- |
| Process instances | `startDate` | `startDate`, `endDate` |
| User tasks | `creationDate` | `creationDate`, `completionDate`, `followUpDate`, `dueDate` |
| Incidents | `creationTime` | `creationTime` |
| Jobs | `creationTime` | `creationTime`, `lastUpdateTime` |
```bash
# Process instances that ended in January
c8 search pi --between=2025-01-01..2025-01-31 --dateField=endDate
# User tasks due this week
c8 search ut --between=2025-03-03..2025-03-07 --dateField=dueDate
# Incidents created today
c8 search inc --between=2025-03-05..2025-03-05
# Jobs created in a date range
c8 search jobs --between=2025-01-01..2025-12-31
```
`--between` also works with the `list` command:
```bash
c8 list pi --between=2025-01-01..2025-03-31
c8 list ut --between=2025-03-01..2025-03-31
c8 list inc --between=2025-03-05..2025-03-05
c8 list jobs --between=2025-01-01..2025-12-31
```
### Wildcard search
String filters support wildcard matching:
- `*` — matches zero or more characters.
- `?` — matches exactly one character.
```bash
c8 search pd --name='*order*'
c8 search pd --id='process-v?'
c8 search jobs --type='*-service'
c8 search variables --name='order*'
```
Wildcard-capable fields per resource:
| Resource | Fields |
| :------------------ | :----------------------- |
| Process definitions | `--name`, `--id` |
| Process instances | `--id` |
| User tasks | `--assignee` |
| Incidents | `--errorMessage`, `--id` |
| Jobs | `--type` |
| Variables | `--name`, `--value` |
### Case-insensitive search
Prefix a flag name with `i` to make the filter case-insensitive. Case-insensitive filtering is performed client-side after fetching results.
```bash
c8 search pd --iname='*ORDER*'
c8 search ut --iassignee=John
c8 search jobs --itype='*Service*'
c8 search inc --ierrorMessage='*timeout*'
c8 search variables --iname='OrderId'
```
Case-insensitive flags per resource:
| Resource | Flags |
| :------------------ | :------------------------- |
| Process definitions | `--iname`, `--iid` |
| Process instances | `--iid` |
| User tasks | `--iassignee` |
| Incidents | `--ierrorMessage`, `--iid` |
| Jobs | `--itype` |
| Variables | `--iname`, `--ivalue` |
:::note
Case-insensitive filtering fetches up to 1000 results from the server and filters client-side. For large result sets, combine with case-sensitive filters to narrow results first.
:::
### Search process definitions
```bash
c8 search pd --id=order-process
c8 search pd --name=Order
c8 search pd --key=2251799813685249
c8 search pd --id=order-process --name=Order
# Using a specific profile for this search
c8 search pd --id=order-process --profile=prod
```
### Search process instances
```bash
c8 search pi --state=ACTIVE
c8 search pi --id=order-process
c8 search pi --processDefinitionKey=2251799813685249
c8 search pi --parentProcessInstanceKey=2251799813685250
c8 search pi --id=order-process --state=ACTIVE
# Filter by date range
c8 search pi --between=2025-01-01..2025-03-31
c8 search pi --between=2025-01-01..2025-06-30 --dateField=endDate
```
### Search user tasks
```bash
c8 search ut --state=CREATED
c8 search ut --assignee=john.doe
c8 search ut --processInstanceKey=2251799813685249
c8 search ut --elementId=UserTask_Approve
c8 search ut --state=CREATED --assignee=john.doe
# Filter by date range
c8 search ut --between=2025-03-01..2025-03-31
c8 search ut --between=2025-03-01..2025-03-31 --dateField=dueDate
```
### Search incidents
```bash
c8 search inc --state=ACTIVE
c8 search inc --processInstanceKey=2251799813685249
c8 search inc --errorType=JOB_NO_RETRIES
c8 search inc --errorMessage='*timeout*'
c8 search inc --state=ACTIVE --errorType=JOB_NO_RETRIES
# Filter by creation time
c8 search inc --between=2025-03-01..2025-03-05
```
### Search jobs
```bash
c8 search jobs --type=email-service
c8 search jobs --state=CREATED
c8 search jobs --processInstanceKey=2251799813685249
c8 search jobs --type=email-service --state=CREATED
# Filter by date range
c8 search jobs --between=2025-01-01..2025-12-31
c8 search jobs --between=2025-01-01..2025-12-31 --dateField=lastUpdateTime
```
### Search variables
```bash
c8 search variables --name=orderId
c8 search variables --value=12345
c8 search variables --processInstanceKey=2251799813685249
c8 search variables --scopeKey=2251799813685260
# Show full (non-truncated) variable values
c8 search variables --name=orderPayload --fullValue
```
By default, long variable values are truncated. Truncated values show a `✓` in the "Truncated" column. Use `--fullValue` to see complete values.
## Variables
### Set variables
Set variables on a process instance or element instance scope:
```bash
c8 set variable 2251799813685249 --variables='{"status":"approved"}'
# Set variables in local scope only (no propagation to parent scopes)
c8 set variable 2251799813685249 --variables='{"x":1}' --local
```
The `--variables` flag accepts a JSON object. Use `--local` to restrict the variable scope to the specified element instance.
## Identity management
Manage users, roles, groups, tenants, authorizations, and mapping rules.
### Users
```bash
c8 list users
c8 search users --name=John --email='john@example.com'
c8 get user john
c8 create user --username=john --name='John Doe' --email=john@example.com --password=secret
c8 delete user john
```
### Roles
```bash
c8 list roles
c8 search roles --name=admin
c8 get role my-role
c8 create role --name=my-role
c8 delete role my-role
```
### Groups
```bash
c8 list groups
c8 search groups --name=developers
c8 get group developers
c8 create group --groupId=developers --name=Developers
c8 delete group developers
```
### Tenants
```bash
c8 list tenants
c8 search tenants --name=Production
c8 get tenant prod
c8 create tenant --tenantId=prod --name='Production'
c8 delete tenant prod
```
### Authorizations
```bash
c8 list auth
c8 search auth --ownerId=john --resourceType=process-definition
c8 get auth 123456
c8 create auth --ownerId=john --ownerType=USER --resourceType=process-definition --resourceId='*' --permissions=READ,CREATE
c8 delete auth 123456
```
### Mapping rules
```bash
c8 list mapping-rules
c8 search mapping-rules --claimName=department
c8 get mapping-rule my-rule
c8 create mapping-rule --mappingRuleId=my-rule --name=my-rule --claimName=department --claimValue=engineering
c8 delete mapping-rule my-rule
```
### Assign and unassign
Use `assign` and `unassign` to manage membership between identity resources:
```bash
# Assign a role to a user
c8 assign role admin --to-user=john
# Assign a user to a group
c8 assign user john --to-group=developers
# Assign a group to a tenant
c8 assign group developers --to-tenant=prod
# Unassign a role from a user
c8 unassign role admin --from-user=john
```
Supported assignment targets:
| Resource | `assign` targets | `unassign` sources |
| :------------- | :------------------------------------------------------------ | :-------------------------------------------------------------------- |
| `role` | `--to-user`, `--to-group`, `--to-tenant`, `--to-mapping-rule` | `--from-user`, `--from-group`, `--from-tenant`, `--from-mapping-rule` |
| `user` | `--to-group`, `--to-tenant` | `--from-group`, `--from-tenant` |
| `group` | `--to-tenant` | `--from-tenant` |
| `mapping-rule` | `--to-group`, `--to-tenant` | `--from-group`, `--from-tenant` |
## Messages
### Publish a message
```bash
c8 publish msg order-placed
c8 publish msg order-placed --correlationKey=order-12345
c8 publish msg order-placed --correlationKey=order-12345 --variables='{"orderId":"12345","total":250.00}'
c8 publish msg order-placed --correlationKey=order-12345 --timeToLive=3600000
```
### Correlate a message
`correlate` is an alias for `publish`:
```bash
c8 correlate msg payment-received --correlationKey=order-12345 --variables='{"amount":250.00}'
```
## Forms
Retrieve the form linked to a user task or process definition:
```bash
# Search both user tasks and process definitions
c8 get form 2251799813685251
# User task form only
c8 get form 2251799813685251 --ut
# Start form for a process definition only
c8 get form 2251799813685252 --pd
# Using a specific profile
c8 get form 2251799813685251 --profile=prod
```
When no flag is specified, `c8ctl` searches both types and reports where the form was found.
## Sorting and limiting results
Use `--sortBy`, `--asc`, and `--desc` to control result ordering, and `--limit` to cap the number of results:
```bash
# Sort process instances ascending by key
c8 list pi --sortBy=key --asc
# Sort user tasks descending by creation time
c8 search ut --state=CREATED --sortBy=creationDate --desc
# Limit results
c8 list pi --limit=10
```
## Output
Search and list results display as tables in text mode:
```text
Key | Process ID | State | Version | Tenant ID
2251799813685260 | order-process | ACTIVE | 3 |
2251799813685270 | order-process | ACTIVE | 3 |
Found 2 process instance(s)
```
Switch to JSON for scripting and automation:
```bash
c8 output json
c8 search pi --state=ACTIVE
# [{"processInstanceKey":"2251799813685260", ...}, ...]
```
---
## Command reference
:::warning Alpha feature
`c8ctl` is in alpha and is not intended for production use. Commands and flags may change without notice between releases. See [Getting started](getting-started.md) for details.
:::
## Global Flags
These flags are accepted by every command.
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--help` / `-h` | boolean | | Show help |
| `--version` / `-v` | string | | Show CLI version, or filter by process definition version on supported commands |
| `--profile` | string | | Use a specific profile |
| `--dry-run` | boolean | | Preview the API request without executing |
| `--verbose` | boolean | | Show verbose output |
| `--fields` | string | | Comma-separated list of fields to display |
| `--json` | boolean | | Force JSON output for this invocation (does not persist; overrides session state and C8CTL_OUTPUT_MODE) |
## Resource Aliases
| Alias | Resource |
|-------|----------|
| `auth` | `authorization` |
| `inc` | `incident` |
| `mr` | `mapping-rule` |
| `msg` | `message` |
| `pd` | `process-definition` |
| `pi` | `process-instance` |
| `ut` | `user-task` |
| `vars` | `variable` |
| `var` | `variable` |
## Search Flags
These flags are available on `list` and `search` commands.
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--sortBy` | string | | Sort results by field |
| `--asc` | boolean | | Sort ascending |
| `--desc` | boolean | | Sort descending |
| `--limit` | string | | Maximum number of results |
| `--between` | string | | Date range filter (e.g. 7d, 30d, 2024-01-01..2024-12-31) |
| `--dateField` | string | | Date field for --between filter |
## Commands
### `list`
List resources
**Resources:** pi (process-instance), pd (process-definition), ut (user-task), inc (incident), jobs, profiles (profile), plugins (plugin), users (user), roles (role), groups (group), tenants (tenant), auth (authorization), mapping-rules (mapping-rule)
**Verb-level flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--all` | boolean | | List all (disable pagination limit) |
**Resource-specific flags:**
process-definition (pd)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--bpmnProcessId` | string | | Filter by BPMN process ID |
| `--id` | string | | Filter by BPMN process ID (alias) |
| `--processDefinitionId` | string | | Filter by process definition ID |
| `--name` | string | | Filter by name |
| `--key` | string | | Filter by key |
| `--iid` | string | | Case-insensitive filter by BPMN process ID |
| `--iname` | string | | Case-insensitive filter by name |
process-instance (pi)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--bpmnProcessId` | string | | Filter by BPMN process ID |
| `--id` | string | | Filter by BPMN process ID (alias) |
| `--processDefinitionId` | string | | Filter by process definition ID |
| `--processDefinitionKey` | string | | Filter by process definition key |
| `--state` | string | | Filter by state (ACTIVE, COMPLETED, etc) |
| `--key` | string | | Filter by key |
| `--parentProcessInstanceKey` | string | | Filter by parent process instance key |
| `--iid` | string | | Case-insensitive filter by BPMN process ID |
user-task (ut)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--state` | string | | Filter by state |
| `--assignee` | string | | Filter by assignee |
| `--processInstanceKey` | string | | Filter by process instance key |
| `--processDefinitionKey` | string | | Filter by process definition key |
| `--elementId` | string | | Filter by element ID |
| `--iassignee` | string | | Case-insensitive filter by assignee |
incident (inc)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--state` | string | | Filter by state |
| `--processInstanceKey` | string | | Filter by process instance key |
| `--processDefinitionKey` | string | | Filter by process definition key |
| `--bpmnProcessId` | string | | Filter by BPMN process ID |
| `--id` | string | | Filter by BPMN process ID (alias) |
| `--processDefinitionId` | string | | Filter by process definition ID |
| `--errorType` | string | | Filter by error type |
| `--errorMessage` | string | | Filter by error message |
| `--ierrorMessage` | string | | Case-insensitive filter by error message |
| `--iid` | string | | Case-insensitive filter by BPMN process ID |
jobs
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--state` | string | | Filter by state |
| `--type` | string | | Filter by job type |
| `--processInstanceKey` | string | | Filter by process instance key |
| `--processDefinitionKey` | string | | Filter by process definition key |
| `--itype` | string | | Case-insensitive filter by job type |
user
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--username` | string | | Filter by username |
| `--name` | string | | Filter by name |
| `--email` | string | | Filter by email |
role
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--roleId` | string | | Filter by role ID |
| `--name` | string | | Filter by name |
group
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--groupId` | string | | Filter by group ID |
| `--name` | string | | Filter by name |
tenant
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--tenantId` | string | | Filter by tenant ID |
| `--name` | string | | Filter by name |
authorization (auth)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--ownerId` | string | | Filter by owner ID |
| `--ownerType` | string | | Filter by owner type |
| `--resourceType` | string | | Filter by resource type |
| `--resourceId` | string | | Filter by resource ID |
mapping-rule (mr)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--mappingRuleId` | string | | Filter by mapping rule ID |
| `--name` | string | | Filter by name |
| `--claimName` | string | | Filter by claim name |
| `--claimValue` | string | | Filter by claim value |
**Examples:**
```bash
c8ctl list pi # List process instances
c8ctl list pd # List process definitions
c8ctl list users # List users
```
---
### `search`
Search resources with filters (wildcards, date ranges, case-insensitive)
**Resources:** pi (process-instance), pd (process-definition), ut (user-task), inc (incident), jobs, vars (variable), users (user), roles (role), groups (group), tenants (tenant), auth (authorization), mapping-rules (mapping-rule)
**Resource-specific flags:**
process-definition (pd)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--bpmnProcessId` | string | | Filter by BPMN process ID |
| `--id` | string | | Filter by BPMN process ID (alias) |
| `--processDefinitionId` | string | | Filter by process definition ID |
| `--name` | string | | Filter by name |
| `--key` | string | | Filter by key |
| `--iid` | string | | Case-insensitive filter by BPMN process ID |
| `--iname` | string | | Case-insensitive filter by name |
process-instance (pi)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--bpmnProcessId` | string | | Filter by BPMN process ID |
| `--id` | string | | Filter by BPMN process ID (alias) |
| `--processDefinitionId` | string | | Filter by process definition ID |
| `--processDefinitionKey` | string | | Filter by process definition key |
| `--state` | string | | Filter by state (ACTIVE, COMPLETED, etc) |
| `--key` | string | | Filter by key |
| `--parentProcessInstanceKey` | string | | Filter by parent process instance key |
| `--iid` | string | | Case-insensitive filter by BPMN process ID |
user-task (ut)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--state` | string | | Filter by state |
| `--assignee` | string | | Filter by assignee |
| `--processInstanceKey` | string | | Filter by process instance key |
| `--processDefinitionKey` | string | | Filter by process definition key |
| `--elementId` | string | | Filter by element ID |
| `--iassignee` | string | | Case-insensitive filter by assignee |
incident (inc)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--state` | string | | Filter by state |
| `--processInstanceKey` | string | | Filter by process instance key |
| `--processDefinitionKey` | string | | Filter by process definition key |
| `--bpmnProcessId` | string | | Filter by BPMN process ID |
| `--id` | string | | Filter by BPMN process ID (alias) |
| `--processDefinitionId` | string | | Filter by process definition ID |
| `--errorType` | string | | Filter by error type |
| `--errorMessage` | string | | Filter by error message |
| `--ierrorMessage` | string | | Case-insensitive filter by error message |
| `--iid` | string | | Case-insensitive filter by BPMN process ID |
jobs
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--state` | string | | Filter by state |
| `--type` | string | | Filter by job type |
| `--processInstanceKey` | string | | Filter by process instance key |
| `--processDefinitionKey` | string | | Filter by process definition key |
| `--itype` | string | | Case-insensitive filter by job type |
variable (var, vars)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--name` | string | | Filter by variable name |
| `--value` | string | | Filter by value |
| `--processInstanceKey` | string | | Filter by process instance key |
| `--scopeKey` | string | | Filter by scope key |
| `--fullValue` | boolean | | Return full variable values (not truncated) |
| `--iname` | string | | Case-insensitive filter by name |
| `--ivalue` | string | | Case-insensitive filter by value |
user
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--username` | string | | Filter by username |
| `--name` | string | | Filter by name |
| `--email` | string | | Filter by email |
role
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--roleId` | string | | Filter by role ID |
| `--name` | string | | Filter by name |
group
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--groupId` | string | | Filter by group ID |
| `--name` | string | | Filter by name |
tenant
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--tenantId` | string | | Filter by tenant ID |
| `--name` | string | | Filter by name |
authorization (auth)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--ownerId` | string | | Filter by owner ID |
| `--ownerType` | string | | Filter by owner type |
| `--resourceType` | string | | Filter by resource type |
| `--resourceId` | string | | Filter by resource ID |
mapping-rule (mr)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--mappingRuleId` | string | | Filter by mapping rule ID |
| `--name` | string | | Filter by name |
| `--claimName` | string | | Filter by claim name |
| `--claimValue` | string | | Filter by claim value |
**Examples:**
```bash
c8ctl search pi --state=ACTIVE # Search for active process instances
c8ctl search pd --bpmnProcessId=myProcess # Search process definitions by ID
c8ctl search pd --name='*main*' # Search process definitions with wildcard
c8ctl search ut --assignee=john # Search user tasks assigned to john
c8ctl search inc --state=ACTIVE # Search for active incidents
c8ctl search jobs --type=myJobType # Search jobs by type
c8ctl search jobs --type='*service*' # Search jobs with type containing "service"
c8ctl search variables --name=myVar # Search for variables by name
c8ctl search variables --value=foo # Search for variables by value
c8ctl search variables --processInstanceKey=123 --fullValue # Search variables with full values
c8ctl search pd --iname='*order*' # Case-insensitive search by name
c8ctl search ut --iassignee=John # Case-insensitive search by assignee
```
---
### `get`
Get a resource by key
**Resources:** pi (process-instance), pd (process-definition), inc (incident), topology, form, user, role, group, tenant, auth (authorization), mapping-rule
**Positional arguments:**
- **process-definition:** `` (required)
- **process-instance:** `` (required)
- **incident:** `` (required)
- **user:** `` (required)
- **role:** `` (required)
- **group:** `` (required)
- **tenant:** `` (required)
- **authorization:** `` (required)
- **mapping-rule:** `` (required)
- **form:** `` (required)
**Resource-specific flags:**
process-definition (pd)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--xml` | boolean | | Get BPMN XML (process definitions) |
form
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--userTask` | boolean | | Get form for user task |
| `--ut` | boolean | | Alias for --userTask |
| `--processDefinition` | boolean | | Get form for process definition |
| `--pd` | boolean | | Alias for --processDefinition |
process-instance (pi)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--variables` | boolean | | Include variables in output |
**Examples:**
```bash
c8ctl get pi 123456 # Get process instance by key
c8ctl get pi 123456 --variables # Get process instance with variables
c8ctl get pd 123456 # Get process definition by key
c8ctl get pd 123456 --xml # Get process definition XML
c8ctl get form 123456 # Get form (searches both user task and process definition)
c8ctl get form 123456 --ut # Get form for user task only
c8ctl get form 123456 --pd # Get start form for process definition only
c8ctl get user john # Get user by username
```
---
### `create`
Create a resource (process instance, identity)
**Resources:** pi (process-instance), user, role, group, tenant, auth (authorization), mapping-rule
**Verb-level flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--processDefinitionId` | string | | Process definition ID (BPMN process ID) |
| `--id` | string | | Process definition ID (alias for --processDefinitionId) |
| `--bpmnProcessId` | string | | BPMN process ID (alias for --processDefinitionId) |
| `--variables` | string | | JSON variables |
| `--awaitCompletion` | boolean | | Wait for process to complete |
| `--fetchVariables` | boolean | | Fetch result variables on completion |
| `--requestTimeout` | string | | Await timeout in milliseconds |
| `--username` | string | | Username |
| `--name` | string | | Display name |
| `--email` | string | | Email address |
| `--password` | string | | Password |
| `--roleId` | string | | Role ID |
| `--groupId` | string | | Group ID |
| `--tenantId` | string | | Tenant ID |
| `--mappingRuleId` | string | | Mapping rule ID |
| `--claimName` | string | | Claim name |
| `--claimValue` | string | | Claim value |
**Resource-specific flags:**
authorization (auth)
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--ownerId` | string | Yes | Authorization owner ID |
| `--ownerType` | string | Yes | Authorization owner type |
| `--resourceType` | string | Yes | Authorization resource type |
| `--resourceId` | string | Yes | Authorization resource ID |
| `--permissions` | string | Yes | Comma-separated permissions |
**Examples:**
```bash
c8ctl create pi --id=myProcess # Create a process instance
c8ctl create pi --id=myProcess --awaitCompletion # Create and await completion
c8ctl create user --username=john --name='John Doe' --email=john@example.com --password=secret # Create a user
```
---
### `delete`
Delete a resource by key
**Usage:** `c8ctl delete `
**Resources:** user, role, group, tenant, auth (authorization), mapping-rule
**Positional arguments:**
- **user:** `` (required)
- **role:** `` (required)
- **group:** `` (required)
- **tenant:** `` (required)
- **authorization:** `` (required)
- **mapping-rule:** `` (required)
**Examples:**
```bash
c8ctl delete user john # Delete user
```
---
### `cancel`
Cancel a process instance
**Usage:** `c8ctl cancel `
**Resources:** pi (process-instance)
**Positional arguments:**
- **process-instance:** `` (required)
---
### `await`
Create and await process instance completion (server-side waiting)
**Usage:** `c8ctl await `
**Resources:** pi (process-instance)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--processDefinitionId` | string | | Process definition ID (BPMN process ID) |
| `--id` | string | | Process definition ID (alias for --processDefinitionId) |
| `--bpmnProcessId` | string | | BPMN process ID (alias for --processDefinitionId) |
| `--variables` | string | | JSON variables |
| `--fetchVariables` | boolean | | Fetch result variables on completion |
| `--requestTimeout` | string | | Await timeout in milliseconds |
**Examples:**
```bash
c8ctl await pi --id=myProcess # Create and wait for completion
```
---
### `complete`
Complete a user task or job
**Usage:** `c8ctl complete `
**Resources:** ut (user-task), job
**Positional arguments:**
- **user-task:** `` (required)
- **job:** `` (required)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--variables` | string | | JSON variables |
---
### `fail`
Mark a job as failed with optional error message and retry count
**Resources:** job
**Positional arguments:**
- **job:** `` (required)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--retries` | string | | Remaining retries |
| `--errorMessage` | string | | Error message |
---
### `activate`
Activate jobs of a specific type for processing
**Resources:** jobs
**Positional arguments:**
- **jobs:** `` (required)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--maxJobsToActivate` | string | | Maximum number of jobs to activate |
| `--timeout` | string | | Job timeout in milliseconds |
| `--worker` | string | | Worker name |
---
### `resolve`
Resolve an incident (marks resolved, allows process to continue)
**Resources:** inc (incident)
**Positional arguments:**
- **incident:** `` (required)
---
### `publish`
Publish a message for message correlation
**Resources:** msg (message)
**Positional arguments:**
- **message:** `` (required)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--correlationKey` | string | | Correlation key |
| `--variables` | string | | JSON variables |
| `--timeToLive` | string | | Time to live in milliseconds |
---
### `correlate`
Correlate a message to a specific process instance
**Resources:** msg (message)
**Positional arguments:**
- **message:** `` (required)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--correlationKey` | string | Yes | Correlation key |
| `--variables` | string | | JSON variables |
| `--timeToLive` | string | | Time to live in milliseconds |
---
### `set`
Set variables on an element instance (process instance or flow element scope). Variables are propagated to the outermost scope by default; use --local to restrict to the specified scope.
**Usage:** `c8ctl set variable `
**Resources:** variable
**Positional arguments:**
- **variable:** `` (required)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--variables` | string | Yes | JSON object of variables to set (required) |
| `--local` | boolean | | Set variables in local scope only (default: propagate to outermost scope) |
**Examples:**
```bash
c8ctl set variable 2251799813685249 --variables='{"status":"approved"}' # Set variables on a process instance
c8ctl set variable 2251799813685249 --variables='{"x":1}' --local # Set variables in local scope only
```
---
### `deploy`
Deploy files to Camunda (auto-discovers deployable files in directories)
**Usage:** `c8ctl deploy [path...]`
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--force` | boolean | | Deploy any file type, ignoring the default extension allow-list |
| `--extensions` | string | | Comma-separated list of additional file extensions to include when scanning directories (e.g. .md,.txt). Explicit file paths bypass the extension allow-list. |
| `--all-extensions` | boolean | | Include all server-supported file extensions during directory discovery |
**Examples:**
```bash
c8ctl deploy ./my-process.bpmn # Deploy a BPMN file
```
---
### `run`
Deploy and start a process instance from a BPMN file
**Usage:** `c8ctl run `
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--variables` | string | | JSON variables |
| `--force` | boolean | | Deploy any file type, ignoring the default extension allow-list |
**Examples:**
```bash
c8ctl run ./my-process.bpmn # Deploy and start process
```
---
### `assign`
Assign a resource to a target (--to-user, --to-group, etc.)
**Usage:** `c8ctl assign `
**Resources:** role, user, group, mapping-rule
**Positional arguments:**
- **role:** `` (required)
- **user:** `` (required)
- **group:** `` (required)
- **mapping-rule:** `` (required)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--to-user` | string | | Target user ID |
| `--to-group` | string | | Target group ID |
| `--to-tenant` | string | | Target tenant ID |
| `--to-mapping-rule` | string | | Target mapping rule ID |
**Examples:**
```bash
c8ctl assign role admin --to-user=john # Assign role to user
```
---
### `unassign`
Unassign a resource from a target (--from-user, --from-group, etc.)
**Usage:** `c8ctl unassign `
**Resources:** role, user, group, mapping-rule
**Positional arguments:**
- **role:** `` (required)
- **user:** `` (required)
- **group:** `` (required)
- **mapping-rule:** `` (required)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--from-user` | string | | Source user ID |
| `--from-group` | string | | Source group ID |
| `--from-tenant` | string | | Source tenant ID |
| `--from-mapping-rule` | string | | Source mapping rule ID |
**Examples:**
```bash
c8ctl unassign role admin --from-user=john # Unassign role from user
```
---
### `watch`
Watch files for changes and auto-deploy
**Usage:** `c8ctl watch [path...]`
**Aliases:** `w`
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--force` | boolean | | Continue watching after all deployment errors |
| `--extensions` | string | | Comma-separated list of additional file extensions to watch (merged with defaults, e.g. .md,.txt) |
| `--all-extensions` | boolean | | Watch all server-supported file extensions |
**Examples:**
```bash
c8ctl watch ./src # Watch directory for changes
```
---
### `open`
Open Camunda web app in browser
**Usage:** `c8ctl open `
**Resources:** operate, tasklist, modeler, optimize
**Examples:**
```bash
c8ctl open operate # Open Camunda Operate in browser
c8ctl open tasklist # Open Camunda Tasklist in browser
c8ctl open operate --profile=prod # Open Operate using a specific profile
```
---
### `add`
Add a profile
**Resources:** profile
**Positional arguments:**
- **profile:** `` (required)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--baseUrl` | string | | Cluster base URL |
| `--clientId` | string | | OAuth client ID |
| `--clientSecret` | string | | OAuth client secret |
| `--audience` | string | | OAuth audience |
| `--oAuthUrl` | string | | OAuth token URL |
| `--defaultTenantId` | string | | Default tenant ID |
| `--username` | string | | Basic auth username |
| `--password` | string | | Basic auth password |
| `--from-file` | string | | Import from .env file |
| `--from-env` | boolean | | Import from environment variables |
---
### `remove`
Remove a profile (alias: rm)
**Usage:** `c8ctl remove profile `
**Aliases:** `rm`
**Resources:** profile, plugin
**Positional arguments:**
- **profile:** `` (required)
- **plugin:** `` (required)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--none` | boolean | | Clear active profile |
---
### `load`
Load a c8ctl plugin (npm registry or URL)
**Usage:** `c8ctl load plugin [name|--from url]`
**Resources:** plugin
**Positional arguments:**
- **plugin:** `` (optional)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--from` | string | | Load plugin from URL |
**Examples:**
```bash
c8ctl load plugin my-plugin # Load plugin from npm registry
c8ctl load plugin --from https://github.com/org/plugin # Load plugin from URL
```
---
### `unload`
Unload a c8ctl plugin (npm uninstall wrapper)
**Usage:** `c8ctl unload plugin `
**Aliases:** `rm`
**Resources:** plugin
**Positional arguments:**
- **plugin:** `` (required)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--force` | boolean | | Force unload without confirmation |
---
### `upgrade`
Upgrade a plugin (respects source type)
**Usage:** `c8ctl upgrade plugin [version]`
**Resources:** plugin
**Positional arguments:**
- **plugin:** `` (required), `` (optional)
**Examples:**
```bash
c8ctl upgrade plugin my-plugin # Upgrade plugin to latest version
c8ctl upgrade plugin my-plugin 1.2.3 # Upgrade plugin to a specific version (source-aware)
```
---
### `downgrade`
Downgrade a plugin to a specific version
**Usage:** `c8ctl downgrade plugin `
**Resources:** plugin
**Positional arguments:**
- **plugin:** `` (required), `` (required)
---
### `sync`
Synchronize plugins from registry (rebuild/reinstall)
**Resources:** plugin
**Examples:**
```bash
c8ctl sync plugin # Synchronize plugins
```
---
### `init`
Create a new plugin from TypeScript template
**Resources:** plugin
**Positional arguments:**
- **plugin:** `` (optional)
**Examples:**
```bash
c8ctl init plugin my-plugin # Create new plugin from template (c8ctl-plugin-my-plugin)
```
---
### `doctor`
Surface plugin-loading collisions detected at startup (#363). Reports loaded plugins with their command names, and any first-registration-wins drops (plugin-name or command-name).
**Resources:** plugin
**Examples:**
```bash
c8ctl doctor plugin # List loaded plugins and any load-time collisions
c8ctl doctor plugin --json # Machine-readable doctor output
```
---
### `use`
Set active profile or tenant
**Usage:** `c8ctl use profile|tenant`
**Resources:** profile, tenant
**Positional arguments:**
- **profile:** `` (optional)
- **tenant:** `` (required)
**Flags:**
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--none` | boolean | | Clear active profile/tenant |
**Examples:**
```bash
c8ctl use profile prod # Set active profile
```
---
### `output`
Show or set output format
**Usage:** `c8ctl output [json|text]`
**Resources:** json, text
**Examples:**
```bash
c8ctl output json # Switch to JSON output
```
---
### `completion`
Generate shell completion script
**Usage:** `c8ctl completion bash|zsh|fish|install`
**Resources:** bash, zsh, fish, install
**Resource-specific flags:**
install
| Flag | Type | Required | Description |
|------|------|----------|-------------|
| `--shell` | string | | Shell to install completions for (bash, zsh, fish) |
**Examples:**
```bash
c8ctl completion bash # Generate bash completion script
c8ctl completion install # Auto-detect shell and install completions (auto-refreshes on upgrade)
c8ctl completion install --shell zsh # Install completions for a specific shell
```
---
### `mcp-proxy`
Start a STDIO MCP proxy (bridges local MCP clients to remote Camunda 8)
**Usage:** `c8ctl mcp-proxy [mcp-path]`
---
### `feedback`
Open the feedback page to report issues or request features
---
### `help`
Show help (run 'c8ctl help \' for details)
**Usage:** `c8ctl help [command]`
**Aliases:** `menu`
---
### `which`
Show active profile or output mode
**Resources:** profile, output
**Examples:**
```bash
c8ctl which profile # Show currently active profile
c8ctl which output # Show current output mode
```
---
## Development workflows
:::warning Alpha feature
`c8ctl` is in alpha and is not intended for production use. Commands and flags may change between releases. For more information, see [Getting started](getting-started.md).
:::
`c8ctl` includes commands that support local development and deployment workflows. You can deploy resources, run processes, watch for changes, manage profiles and sessions, and bridge MCP connections for AI assistants.
:::tip
Use the `--profile` flag with any command to run it against a specific cluster without changing the active session.
```bash
c8 deploy ./process.bpmn --profile=staging
c8 run ./order.bpmn --profile=prod
c8 watch --profile=local
```
:::
## Deploy
Deploy resources to the active cluster.
### Deploy a single file
```bash
c8 deploy ./process.bpmn
c8 deploy ./decision.dmn
c8 deploy ./form.form
```
### Deploy multiple files
```bash
c8 deploy ./process1.bpmn ./process2.bpmn ./decision.dmn
```
### Deploy a directory
```bash
# Deploy all resources in the current directory and subdirectories
c8 deploy
# Deploy all resources in a specific directory
c8 deploy ./my-project
```
When scanning directories, `c8ctl` includes files with the following extensions by default:
`.bpmn`, `.dmn`, `.form`, `.md`, `.txt`, `.xml`, `.rpa`, `.json`, `.config`, `.yml`, `.yaml`
Use `--force` to deploy files with any extension:
```bash
c8 deploy ./custom-resource.unsupported --force
```
### Building blocks and process applications
`c8ctl` recognizes two special folder conventions during deployment:
- Building blocks — folders containing `_bb-` in their name. These are deployed first.
- Process applications — folders containing a `.process-application` marker file.
```text
my-project/
├── _bb-shared/
│ ├── common.bpmn
│ └── nested/
│ └── util.bpmn
├── my-app/
│ ├── .process-application
│ ├── process.bpmn
│ └── subfolder/
│ └── form.form
└── standalone.bpmn
```
```bash
c8 deploy ./my-project
```
```text
Deploying 5 resource(s)...
✓ Deployment successful [Key: 123456789]
File | Type | ID | Version | Key
--------------------------------|---------|------------|---------|-------------------
_bb-shared/common.bpmn | Process | common | 1 | 2251799813685249
_bb-shared/nested/util.bpmn | Process | util | 1 | 2251799813685250
my-app/process.bpmn | Process | my-proc | 1 | 2251799813685251
my-app/subfolder/form.form | Form | form-id | 1 | 2251799813685252
standalone.bpmn | Process | standalone | 1 | 2251799813685253
```
Building block resources are listed first, followed by process application resources, then standalone resources.
### Duplicate process ID detection
Camunda does not allow deploying multiple resources with the same process or decision ID in a single deployment. `c8ctl` detects duplicate IDs before sending the request and shows a clear error message indicating which files conflict.
If you have files that share the same ID, deploy them separately:
```bash
c8 deploy process-v1.bpmn
c8 deploy process-v2.bpmn
```
### Exclude files with `.c8ignore`
Create a `.c8ignore` file in your project directory to exclude files and directories from deployment and watch scanning. The format follows the same pattern syntax as `.gitignore`:
```text
# Exclude test resources
tests/
# Exclude work-in-progress files
wip-*.bpmn
# Exclude a specific file
old-process.bpmn
```
Place the `.c8ignore` file in the root of the directory you pass to `c8 deploy` or `c8 watch`. Patterns are matched against relative file paths within that directory.
## Run
The `run` command deploys a file and immediately creates a process instance in a single step:
```bash
c8 run ./order-process.bpmn
# With variables
c8 run ./order-process.bpmn --variables='{"orderId":"12345","amount":100}'
# Deploy a file with an unsupported extension
c8 run ./process.xml --force
```
## Watch
Watch a directory for file changes and auto-redeploy on save:
```bash
c8 watch
# Watch a specific directory
c8 watch ./my-project
# Monitor only specific file extensions
c8 watch --extensions=.bpmn,.dmn,.form
# continue watching current directory
# even when deployment fails
c8 watch --force
```
By default, `c8ctl` monitors the same extensions used by `deploy`. Use `--extensions` to override. Use `--force` to continue watching after deployment errors.
### Continue watching after deployment errors
By default, `c8ctl` stops watching when a deployment fails with an error. Use `--force` to continue watching and redeploy on subsequent file changes, even after errors:
```bash
c8 watch --force
c8 watch ./my-project --force
```
This is useful during active development when your resources may temporarily be in an invalid state.
## Profile management
For full profile management documentation, including adding, listing, switching, and removing profiles, see [Getting started — Profile management](getting-started.md#profile-management).
### Quick reference
```bash
c8 add profile prod --baseUrl=https://camunda.example.com --clientId=xxx --clientSecret=yyy
c8 list profiles
c8 use profile prod
c8 which profile
c8 remove profile prod
```
### One-off profile override
Pass `--profile` to any command to use a different profile for that single invocation. The active session profile is not changed:
```bash
# Run a command against a different cluster
c8 list pi --profile=staging
# Deploy to production without switching context
c8 deploy ./release/ --profile=prod
# Use a Camunda Modeler profile for one command
c8 search ut --state=CREATED --profile=modeler:Cloud Cluster
```
This is useful when you are working against a local development cluster but need to quickly check or interact with another environment.
### Camunda Modeler integration
`c8ctl` automatically discovers and imports profiles from Camunda Modeler. These profiles are read-only, always prefixed with `modeler:`, and loaded dynamically on each command execution.
```bash
# Set a Modeler profile as the active session profile
c8 use profile "modeler:Local Dev"
# Use a Modeler profile for one command
c8 list pi --profile=modeler:Cloud Cluster
# Deploy using a Modeler profile
c8 deploy ./process.bpmn --profile=modeler:Local Dev
```
For Modeler profile file locations per platform, see [Getting started — Camunda Modeler integration](getting-started.md#camunda-modeler-integration).
## Session management
Session state persists between commands. Settings you change remain active until you change them again.
### Set the active profile
```bash
c8 use profile prod
```
### Set the active tenant
```bash
c8 use tenant my-tenant-id
```
### Set the output mode
```bash
c8 output json # JSON output for scripting
c8 output text # human-readable tables (default)
c8 output # show current output mode
```
## MCP proxy
The `mcp-proxy` command starts a local STDIO-to-HTTP proxy that bridges MCP clients (such as VS Code with GitHub Copilot, or Claude Code) to the [Orchestration Cluster MCP Server](/apis-tools/orchestration-cluster-api-mcp/orchestration-cluster-api-mcp-overview.md). It handles OAuth 2.0 authentication transparently, so MCP clients that do not support the client credentials flow can connect to authenticated clusters.
### Configure with VS Code
Add the following to your `.vscode/mcp.json`:
```json
{
"servers": {
"camunda-mcp": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@camunda8/cli", "mcp-proxy"],
"env": {
"CAMUNDA_BASE_URL": "https://",
"CAMUNDA_CLIENT_ID": "",
"CAMUNDA_CLIENT_SECRET": "",
"CAMUNDA_OAUTH_URL": "https:///oauth/token",
"CAMUNDA_TOKEN_AUDIENCE": ""
}
}
}
}
```
| Variable | Description |
| :----------------------- | :--------------------------------------------------------------------------- |
| `CAMUNDA_BASE_URL` | Base URL of your Orchestration Cluster, **without** the `/mcp/cluster` path. |
| `CAMUNDA_CLIENT_ID` | OAuth client ID from your API client credentials. |
| `CAMUNDA_CLIENT_SECRET` | OAuth client secret from your API client credentials. |
| `CAMUNDA_OAUTH_URL` | OAuth token endpoint URL. |
| `CAMUNDA_TOKEN_AUDIENCE` | Token audience for the Orchestration Cluster API. |
:::tip
When you [create API client credentials](/components/hub/organization/manage-clusters/manage-api-clients.md#create-a-client) in the Camunda Console, all required connection details are shown on the credentials page. You can also copy a ready-to-use `c8ctl` configuration snippet from the MCP tab.
:::
### Use a profile with MCP proxy
Instead of passing environment variables, use a `c8ctl` profile to supply credentials:
```json
{
"servers": {
"camunda-mcp": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@camunda8/cli", "mcp-proxy", "--profile=prod"]
}
}
}
```
This reads credentials from the named profile, including Modeler profiles (for example, `--profile=modeler:Cloud Cluster`).
### Local development without authentication
If your local cluster does not require authentication (for example, [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md)), you can connect MCP clients directly without the proxy:
```json
{
"servers": {
"camunda": {
"type": "http",
"url": "http://localhost:8080/mcp/cluster"
}
}
}
```
For full MCP server documentation, see [Orchestration Cluster MCP Server](/apis-tools/orchestration-cluster-api-mcp/orchestration-cluster-api-mcp-overview.md).
## Advanced example: iterative process testing
This example demonstrates an end-to-end development workflow — auto-deploying on save, starting a process instance with variables loaded from a file, monitoring execution in real time, and retrieving the result.
### 1. Start watch mode
In a terminal, start `c8ctl` in watch mode to auto-deploy resources whenever you save changes:
```bash
c8 watch
```
Now edit your `.bpmn`, `.dmn`, or `.form` files in your editor. Every time you save, `c8ctl` redeploys automatically.
### 2. Prepare process variables
In a different terminal, load variables from a JSON file into a shell variable:
```bash
export processVar=$(
:::warning Alpha feature
`c8ctl` is in alpha and is not intended for production use. APIs, commands, and flags may change without notice between releases. See [alpha features](/components/early-access/alpha/alpha-features.md) for more information. Report issues and request features in the [`c8ctl` GitHub repository](https://github.com/camunda/c8ctl).
:::
## About
`c8ctl` is a minimal-dependency CLI for Camunda 8. It is built on top of the [`@camunda8/orchestration-cluster-api`](https://www.npmjs.com/package/@camunda8/orchestration-cluster-api) TypeScript SDK and provides two equivalent bin aliases: `c8ctl` and `c8`.
`c8ctl` is designed for developers who need fast, scriptable access to a Camunda 8 cluster during development and testing. It supports both Camunda 8 SaaS and Self-Managed environments.
Use `c8ctl` to:
- Inspect running clusters — list process instances, user tasks, incidents, and jobs.
- Deploy BPMN, DMN, and form resources, optionally watching for file changes.
- Manage profiles for multiple clusters, including profiles imported from Camunda Modeler.
- Extend the CLI with custom plugins.
## Prerequisites
- **Node.js ≥ 22.18.0** (required for native TypeScript support)
## Install
Install `c8ctl` globally from npm:
```bash
npm install @camunda8/cli -g
```
After installation, both `c8ctl` and `c8` are available as commands in your terminal.
## Quick start with a local cluster
`c8ctl` includes a built-in `cluster` command that downloads and manages a local [Camunda 8 Run](/self-managed/quickstart/developer-quickstart/c8run.md) instance. This is the fastest way to get a cluster running for development.
### Start a cluster
```bash
# Start with the latest stable version (default)
c8 cluster start
# Start with a specific version
c8 cluster start 8.9.0-alpha5
# Start using a version alias
c8 cluster start stable
c8 cluster start alpha
# Start with a major.minor version (rolling release)
c8 cluster start 8.8
```
`c8ctl` automatically downloads the correct binary for your platform, caches it locally, launches the cluster in the background, and waits for it to become healthy.
### Stop the cluster
```bash
c8 cluster stop
```
### Check cluster status
```bash
c8 cluster status
```
Reports whether a cluster is running, including connection details.
### View cluster logs
```bash
c8 cluster logs
```
Streams log output from the running cluster.
### Manage cached versions
```bash
# List locally cached versions and available aliases
c8 cluster list
# List all versions available on the remote download server
c8 cluster list-remote
# Download a version without starting it
c8 cluster install 8.8
# Remove a locally cached version
c8 cluster delete 8.8
```
### Version aliases
The `stable` and `alpha` aliases are resolved dynamically from the [Camunda Download Center](https://downloads.camunda.cloud/release/camunda/c8run/):
| Alias | Resolves to |
| :------- | :------------------------------------------------------- |
| `stable` | Highest minor release that is GA (for example, 8.9) |
| `alpha` | Highest minor release overall (for example, 8.10-alpha0) |
When no version is specified, `c8 cluster start` defaults to `stable`.
A `.` version like `8.8` is treated as a rolling release — the download server directory is updated in-place with new patch releases. `c8 cluster start` uses the local version if available, while `c8 cluster install` always checks for a newer version.
### Debug output
Stream raw c8run logs during startup:
```bash
c8 cluster start --debug
```
### Supported platforms
- macOS (x86_64, aarch64)
- Linux (x86_64, aarch64)
- Windows (x86_64)
Cache locations:
| Platform | Path |
| :------- | :---------------------------- |
| macOS | `~/Library/Caches/c8run/` |
| Linux | `~/.cache/c8run/` |
| Windows | `%LOCALAPPDATA%\c8run\cache\` |
Override the cache directory with the `C8RUN_CACHE_DIR` environment variable.
## Credential resolution
`c8ctl` resolves credentials in the following order:
1. **`--profile` flag** — one-off override for a single command.
2. **Active profile** — set with `c8 use profile `.
3. **Environment variables** — standard `CAMUNDA_*` variables (take precedence over the default profile).
4. **Default `local` profile** — `http://localhost:8080/v2`.
When no profile has been explicitly set, `c8ctl` defaults to a built-in `local` profile that points to `http://localhost:8080/v2`. This means you can start a local cluster with `c8 cluster start` and immediately run commands without any configuration. If the connection fails, `c8ctl` shows a hint with the URL it tried to connect to.
### Use environment variables
```bash
export CAMUNDA_BASE_URL=https://camunda.example.com
export CAMUNDA_CLIENT_ID=your-client-id
export CAMUNDA_CLIENT_SECRET=your-client-secret
c8 list pi
```
### Use a profile
```bash
c8 add profile prod \
--baseUrl=https://camunda.example.com \
--clientId=your-client-id \
--clientSecret=your-client-secret
c8 use profile prod
c8 list pi
```
### Override the profile for a single command
Pass `--profile` to any command to use a different profile without changing the active session:
```bash
c8 list pi --profile=staging
c8 deploy ./process.bpmn --profile=prod
c8 search ut --assignee=jane --profile=dev
```
The `--profile` flag works with both `c8ctl` profiles and Camunda Modeler profiles (prefixed with `modeler:`):
```bash
c8 list pi --profile=modeler:Cloud Cluster
c8 deploy ./process.bpmn --profile=modeler:Local Dev
```
## Tenant resolution
Tenants are resolved in the following order:
1. **Active tenant** — set with `c8 use tenant `.
2. **Default tenant** from the active profile.
3. **`CAMUNDA_DEFAULT_TENANT_ID`** environment variable.
4. **``** tenant.
```bash
c8 use tenant my-tenant-id
c8 list pi # uses my-tenant-id
```
## Profile management
`c8ctl` supports two types of profiles:
1. `c8ctl` profiles — managed directly with `c8ctl` commands.
2. Camunda Modeler profiles — automatically imported from Camunda Modeler (read-only, prefixed with `modeler:`).
### Add a profile
```bash
# Minimal local profile (defaults to http://localhost:8080/v2)
c8 add profile local
# OAuth-secured cluster
c8 add profile prod \
--baseUrl=https://camunda.example.com \
--clientId=your-client-id \
--clientSecret=your-client-secret
# With explicit OAuth endpoint and audience
c8 add profile prod \
--baseUrl=https://camunda.example.com \
--clientId=your-client-id \
--clientSecret=your-client-secret \
--audience=camunda-api \
--oAuthUrl=https://auth.example.com/oauth/token
# With a default tenant
c8 add profile dev \
--baseUrl=https://dev.example.com \
--clientId=dev-client \
--clientSecret=dev-secret \
--defaultTenantId=dev-tenant
```
### List profiles
```bash
c8 list profiles
```
Lists both `c8ctl` and Modeler profiles. Modeler profiles appear with a `modeler:` prefix.
### Switch the active profile
```bash
c8 use profile prod
c8 use profile "modeler:Local Dev"
```
All subsequent commands use the active profile until you switch again or pass `--profile`.
### Show the current profile
```bash
c8 which profile
```
### Remove a profile
```bash
c8 remove profile prod
c8 rm profile prod # alias
```
:::note
Modeler profiles are read-only. They cannot be modified or removed through `c8ctl` — manage them in Camunda Modeler.
:::
### Camunda Modeler integration
`c8ctl` automatically reads profiles from Camunda Modeler's `profiles.json` file. These profiles are:
- **Read-only** — cannot be modified or deleted via `c8ctl`.
- **Prefixed** — always displayed with a `modeler:` prefix (for example, `modeler:Local Dev`).
- **Dynamic** — loaded fresh on each command execution.
Platform-specific locations:
| Platform | Path |
| :------- | :------------------------------------------------------------ |
| Linux | `~/.config/camunda-modeler/profiles.json` |
| macOS | `~/Library/Application Support/camunda-modeler/profiles.json` |
| Windows | `%APPDATA%\camunda-modeler\profiles.json` |
```bash
# Use a Modeler profile as the active session profile
c8 use profile "modeler:Local Dev"
# Use a Modeler profile for a single command
c8 list pi --profile=modeler:Cloud Cluster
```
## Get help
```bash
c8ctl help # general help
c8ctl help list # help for the list command
c8ctl help deploy # help for the deploy command
c8ctl help profiles # help for profile management
c8ctl --version # print version
```
Run any verb without a resource to see what resources are available:
```bash
c8 list # shows: pi, pd, ut, inc, jobs, profiles, plugins, users, roles, groups, tenants, auth, mr
c8 search # shows: pi, pd, ut, inc, jobs, variables, users, roles, groups, tenants, auth, mr
```
## Send feedback
```bash
c8 feedback
```
Opens the GitHub issues page in your browser to report bugs or request features.
## Update notifications
`c8ctl` checks for newer versions in the background and displays a one-time notification when an update is available. This check is suppressed in CI environments, JSON output mode, and development versions.
## Shell completion
The recommended way to set up shell completion is with the `install` subcommand:
```bash
c8 completion install
```
This auto-detects your shell, writes the completion file, and wires it into your shell configuration. To specify a shell explicitly:
```bash
c8 completion install --shell zsh
```
Completions auto-refresh when the CLI is upgraded.
Alternatively, generate the completion script manually:
```bash
c8ctl completion bash > ~/.c8ctl-completion.bash
echo 'source ~/.c8ctl-completion.bash' >> ~/.bashrc
source ~/.bashrc
```
```bash
c8ctl completion zsh > ~/.c8ctl-completion.zsh
echo 'source ~/.c8ctl-completion.zsh' >> ~/.zshrc
source ~/.zshrc
```
```bash
c8ctl completion fish > ~/.config/fish/completions/c8ctl.fish
```
Fish loads the completion automatically on the next shell start.
## Output modes
Switch between human-readable text and machine-readable JSON:
```bash
c8 output json # all commands output JSON
c8 output text # back to formatted tables (default)
```
## Environment variables
| Variable | Description |
| :-------------------------- | :------------------- |
| `CAMUNDA_BASE_URL` | Cluster base URL |
| `CAMUNDA_CLIENT_ID` | OAuth client ID |
| `CAMUNDA_CLIENT_SECRET` | OAuth client secret |
| `CAMUNDA_TOKEN_AUDIENCE` | OAuth token audience |
| `CAMUNDA_OAUTH_URL` | OAuth token endpoint |
| `CAMUNDA_DEFAULT_TENANT_ID` | Default tenant ID |
Environment variable conventions follow the [`@camunda8/orchestration-cluster-api`](https://www.npmjs.com/package/@camunda8/orchestration-cluster-api) module.
## Debug mode
Enable debug logging to see detailed internal information such as plugin loading and credential resolution:
```bash
DEBUG=1 c8 list pi
# or
C8CTL_DEBUG=true c8 list pi
```
Debug output is written to stderr and does not interfere with normal command output.
## Next steps
- [Cluster inspection and process management](cluster-inspection.md) — list, search, and manage process instances, user tasks, incidents, and jobs.
- [Development workflows](development-workflows.md) — deploy, run, watch, and configure profiles and MCP proxy.
- [Extend `c8ctl` with plugins](plugins.md) — scaffold, install, and manage custom CLI plugins.
---
## Identity management
:::warning Alpha feature
`c8ctl` is in alpha and not intended for production use. Commands and flags may change between releases. See [Getting started](getting-started.md) for details.
:::
`c8ctl` provides commands to manage identity resources through the Orchestration Cluster API. You can list, search, get, create, and delete users, roles, groups, tenants, authorizations, and mapping rules. Membership management is handled with the `assign` and `unassign` verbs.
| Resource | Alias | Available verbs |
| :----------------- | :----- | :------------------------------------------ |
| `user(s)` | — | `list`, `search`, `get`, `create`, `delete` |
| `role(s)` | — | `list`, `search`, `get`, `create`, `delete` |
| `group(s)` | — | `list`, `search`, `get`, `create`, `delete` |
| `tenant(s)` | — | `list`, `search`, `get`, `create`, `delete` |
| `authorization(s)` | `auth` | `list`, `search`, `get`, `create`, `delete` |
| `mapping-rule(s)` | `mr` | `list`, `search`, `get`, `create`, `delete` |
:::tip
All commands respect the active profile and tenant. Pass `--profile` to override the profile for a single command:
```bash
c8 list users --profile=prod
c8 search roles --profile=staging
```
:::
## Users
### List users
```bash
c8 list users
```
### Search users
```bash
c8 search users --name=John
c8 search users --email='john@example.com'
c8 search users --name=John --email='john@example.com'
```
### Get a user
```bash
c8 get user john
```
### Create a user
```bash
c8 create user --username=john --name='John Doe' --email=john@example.com --password=changeme
```
### Delete a user
```bash
c8 delete user john
```
## Roles
### List roles
```bash
c8 list roles
```
### Search roles
```bash
c8 search roles --name=admin
```
### Get a role
```bash
c8 get role admin
```
### Create a role
```bash
c8 create role --name=my-role
```
### Delete a role
```bash
c8 delete role my-role
```
## Groups
### List groups
```bash
c8 list groups
```
### Search groups
```bash
c8 search groups --name=developers
```
### Get a group
```bash
c8 get group developers
```
### Create a group
```bash
c8 create group --groupId=developers --name=Developers
```
### Delete a group
```bash
c8 delete group developers
```
## Tenants
### List tenants
```bash
c8 list tenants
```
### Search tenants
```bash
c8 search tenants --name=Production
```
### Get a tenant
```bash
c8 get tenant prod
```
### Create a tenant
```bash
c8 create tenant --tenantId=prod --name='Production'
```
### Delete a tenant
```bash
c8 delete tenant prod
```
## Authorizations
### List authorizations
```bash
c8 list auth
c8 list authorizations
```
### Search authorizations
```bash
c8 search auth --ownerId=john --resourceType=process-definition
```
### Create an authorization
```bash
c8 create auth --ownerId=john --ownerType=USER --resourceType=process-definition --resourceId='*' --permissions=READ,CREATE
```
### Delete an authorization
```bash
c8 delete auth 2251799813685260
```
## Mapping rules
### List mapping rules
```bash
c8 list mr
c8 list mapping-rules
```
### Search mapping rules
```bash
c8 search mr --name=my-rule
```
### Create a mapping rule
```bash
c8 create mr --mappingRuleId=my-rule --name='My Rule'
```
### Delete a mapping rule
```bash
c8 delete mr my-rule
```
## Assign and unassign
The `assign` and `unassign` verbs manage membership between identity resources. You can assign users to roles, groups, or tenants, and assign groups to tenants.
### Assign a user to a role
```bash
c8 assign role admin --to-user=john
```
### Unassign a user from a role
```bash
c8 unassign role admin --from-user=john
```
### Assign a user to a group
```bash
c8 assign user john --to-group=developers
```
### Unassign a user from a group
```bash
c8 unassign user john --from-group=developers
```
### Assign a group to a tenant
```bash
c8 assign group developers --to-tenant=prod
```
### Unassign a group from a tenant
```bash
c8 unassign group developers --from-tenant=prod
```
---
## Extend c8ctl with plugins
:::warning Alpha feature
`c8ctl` is in alpha and not intended for production use. Commands and flags may change between releases. See [Getting started](getting-started.md) for details.
:::
`c8ctl` supports a global plugin system that lets you add custom commands. Plugins are installed globally to a user-specific directory and tracked in a registry file (`plugins.json`).
## Plugin storage locations
| Platform | Plugins directory | Registry file |
| :------- | :--------------------------------------------------------- | :------------------------------------------------- |
| Linux | `~/.config/c8ctl/plugins/node_modules` | `~/.config/c8ctl/plugins.json` |
| macOS | `~/Library/Application Support/c8ctl/plugins/node_modules` | `~/Library/Application Support/c8ctl/plugins.json` |
| Windows | `%APPDATA%\c8ctl\plugins\node_modules` | `%APPDATA%\c8ctl\plugins.json` |
You can override the data directory with the `C8CTL_DATA_DIR` environment variable.
## Scaffold a new plugin
Generate a new plugin project from a TypeScript template:
```bash
c8ctl init plugin my-plugin
```
This creates a project directory with all necessary files, build configuration, and an `AGENTS.md` guide for autonomous plugin implementation.
## Install a plugin
### From the npm registry
```bash
c8 load plugin my-custom-plugin
```
### From a URL
```bash
c8 load plugin --from https://github.com/user/my-plugin
c8 load plugin --from file:///path/to/local/plugin
c8 load plugin --from git://github.com/user/plugin.git
```
After loading, plugin commands are immediately available.
## Manage plugins
### List installed plugins
```bash
c8 list plugins
```
Output shows version and sync status for each plugin:
- `✓ Installed` — plugin is in the registry and installed.
- `⚠ Not installed` — plugin is in the registry but missing from disk (run `sync`).
- `⚠ Not in registry` — plugin is installed but not tracked in the registry.
### Upgrade a plugin
```bash
# Upgrade to latest
c8 upgrade plugin my-custom-plugin
# Upgrade to a specific version
c8 upgrade plugin my-custom-plugin 1.2.3
```
### Downgrade a plugin
```bash
c8 downgrade plugin my-custom-plugin 1.0.0
```
Upgrade and downgrade behavior depends on the plugin source:
| Source | Behavior |
| :---------- | :---------------------------------------------------------------------------------------------------------- |
| npm package | Installs `@`. |
| URL/git | Installs `#`. |
| `file://` | Version-based upgrade/downgrade is not supported. Use `load plugin --from` with the desired local checkout. |
### Unload a plugin
```bash
c8 unload plugin my-custom-plugin
```
### Synchronize plugins
Synchronize all plugins from the registry. Rebuilds installed plugins and reinstalls any that are missing:
```bash
c8 sync plugins
```
## Plugin structure
A plugin is a regular Node.js module with a `c8ctl-plugin.js` (or `c8ctl-plugin.ts`) file in the root directory. The file must export a `commands` object and optionally a `metadata` object.
### Minimal example
```typescript
// c8ctl-plugin.ts
export const metadata = {
name: "my-plugin",
description: "My custom c8ctl plugin",
commands: {
analyze: {
description: "Analyze BPMN processes for best practices",
},
optimize: {
description: "Optimize process definitions",
},
},
};
export const commands = {
analyze: async (args: string[]) => {
console.log("Analyzing...", args);
},
optimize: async (args: string[]) => {
console.log("Optimizing...");
},
};
```
## Plugin runtime API
At runtime, `c8ctl` injects a global object via `globalThis.c8ctl` that plugins can use to interact with the Camunda cluster and the `c8ctl` environment.
| Method/field | Description |
| :----------------------------------- | :--------------------------------------------------------------------------------------- |
| `createClient(profile?, sdkConfig?)` | Create a Camunda SDK client. Optionally pass a profile name to use specific credentials. |
| `resolveTenantId(profile?)` | Resolve the active tenant ID using the same fallback logic as built-in commands. |
| `getLogger()` | Get the `c8ctl` logger instance (respects the current output mode). |
| `version` | `c8ctl` version string. |
| `nodeVersion` | Node.js version. |
| `platform` | Operating system (`linux`, `darwin`, `win32`). |
| `arch` | CPU architecture. |
| `cwd` | Current working directory. |
| `outputMode` | Current output mode (`text` or `json`). |
| `activeProfile` | Name of the active profile. |
| `activeTenant` | Active tenant ID. |
### TypeScript autocomplete
For TypeScript autocomplete in your plugin, import the runtime type:
```typescript
const c8ctl = globalThis.c8ctl as C8ctlPluginRuntime;
const tenantId = c8ctl.resolveTenantId();
const logger = c8ctl.getLogger();
logger.info(`Tenant: ${tenantId}`);
```
### Use the SDK client from a plugin
```typescript
const c8ctl = globalThis.c8ctl as C8ctlPluginRuntime;
export const commands = {
"list-active": async (args: string[]) => {
const client = c8ctl.createClient();
const logger = c8ctl.getLogger();
// Use the client to query the Orchestration Cluster API
logger.info("Client ready");
},
};
```
## Help integration
When plugins export a `metadata.commands` object with descriptions, those commands appear in the `c8ctl help` output under a **Plugin Commands** section:
```text
c8ctl - Camunda 8 CLI v2.2.0
Commands:
list List resources (pi, ut, inc, jobs, profiles)
get Get resource by key (pi, topology)
...
Plugin Commands:
analyze Analyze BPMN processes for best practices
optimize Optimize process definitions
```
Plugins without a `metadata` export still work — their commands appear in the help output without descriptions.
## Command precedence
Built-in commands take precedence over plugin commands. If a plugin exports a command with the same name as a built-in command (for example, `list` or `deploy`), the built-in command runs.
Use descriptive and unique names for plugin commands.
Recommended:
- `analyze-process`
- `export-data`
- `sync-resources`
Avoid:
- `list`
- `get`
- `create`
- `deploy`
## Find plugins
Plugins are distributed as regular npm packages. There are two main ways to discover available plugins:
### Search the Camunda GitHub organization
Browse the [Camunda GitHub organization](https://github.com/camunda) and search for repositories with `c8ctl` in the name. By convention, plugin repositories are named `c8ctl-plugin-` (for example, `c8ctl-plugin-analyze`), but this is not a hard requirement — any npm package with a `c8ctl-plugin.js` entry point works as a plugin.
### Search the npm registry
Search for `c8ctl` or `c8ctl-plugin` on [npmjs.com](https://www.npmjs.com/search?q=c8ctl-plugin):
```bash
npm search c8ctl-plugin
```
Once you find a plugin, install it with:
```bash
c8 load plugin
```
## Best practices
- Use unique command names to avoid conflicts with built-in commands.
- Provide descriptions in `metadata.commands` so users discover your commands in `c8ctl help`.
- Keep descriptions concise and aim for a single line under 60 characters, starting with an imperative verb.
- Transpile TypeScript to JavaScript before publishing. The `c8ctl-plugin.js` entry point in `node_modules` must be JavaScript, because Node.js does not support type stripping in `node_modules`.
- Use `createClient()` from the runtime API to create SDK clients rather than importing the SDK directly. This ensures credentials and tenant resolution follow `c8ctl` conventions.
---
## Configuration
This page uses YAML examples to show configuration properties. Alternate methods to [externalize or override your configuration](https://docs.spring.io/spring-boot/reference/features/external-config.html) are provided by Spring Boot, and can be applied without rebuilding your application (properties files, Java System properties, or environment variables).
:::note
Configuration properties can be defined as environment variables using [Spring Boot conventions](https://docs.spring.io/spring-boot/reference/features/external-config.html#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables). To define an environment variable, convert the configuration property to uppercase, remove any dashes `-`, and replace any delimiters `.` with underscore `_`.
For example, the property `camunda.client.worker.defaults.max-jobs-active` is represented by the environment variable `CAMUNDA_CLIENT_WORKER_DEFAULTS_MAXJOBSACTIVE`.
:::
:::note
For a full set of properties, head over to the [properties reference](./properties-reference.md)
:::
## Modes
The Camunda Spring Boot Starter has modes with meaningful defaults aligned with the distribution's default connection details. Each mode is made for a Camunda 8 setup, and only one mode may be used at a time.
:::note
The defaults applied by the modes are overwritten by _any_ other set property, including legacy/deprecated properties. Check your configuration and logs to avoid unwanted override.
:::
### SaaS
This allows you to connect to a Camunda instance in our SaaS offering as the URLs are templated.
Activate by setting:
```yaml
camunda:
client:
mode: saas
```
This applies the following defaults:
```yaml reference referenceLinkText="Source" title="SaaS mode"
https://github.com/camunda/camunda/blob/main/clients/camunda-spring-boot-starter/src/main/resources/modes/saas.yaml
```
The only thing you need to configure then, are the connection details to your Camunda SaaS cluster:
```yaml
camunda:
client:
auth:
client-id:
client-secret:
cloud:
cluster-id:
region:
```
Other connectivity configuration does not further apply for the SaaS mode.
### Self-Managed
This allows you to connect to a Self-Managed instance protected with JWT authentication. The default URLs are configured to align with all Camunda distributions using `localhost` addresses.
Activate by setting:
```yaml
camunda:
client:
mode: self-managed
```
This applies the following defaults:
```yaml reference referenceLinkText="Source" title="Self-managed mode"
https://github.com/camunda/camunda/blob/main/clients/camunda-spring-boot-starter/src/main/resources/modes/self-managed.yaml
```
For some specific OIDC setups (e.g [Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity)), you might need to define additional properties like `camunda.client.auth.scope` in addition to the defaults provided by the mode, see the [`camunda.client.auth`-Properties reference](./properties-reference.md) for a full overview.
## Connectivity
The connection to Camunda API is determined by `camunda.client.grpc-address` and `camunda.client.rest-address`
### Camunda API connection
#### gRPC address
Define the address of the [gRPC API](/apis-tools/zeebe-api/grpc.md) exposed by the [Zeebe Gateway](/reference/glossary.md#zeebe-gateway):
```yaml
camunda:
client:
grpc-address: http://localhost:26500
```
:::note
You must add the `http://` scheme to the URL to avoid a `java.lang.NullPointerException: target` error.
:::
#### REST address
Define address of the [Orchestration Cluster REST API](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md) exposed by the Zeebe Gateway:
```yaml
camunda:
client:
rest-address: http://localhost:8080
```
:::note
You must add the `http://` scheme to the URL to avoid a `java.lang.NullPointerException: target` error.
:::
#### Prefer REST over gRPC
By default, the Camunda Client will use REST instead of gRPC whenever possible to communicate with the Camunda APIs.
To use the gRPC by default, you can configure this:
```yaml
camunda:
client:
prefer-rest-over-grpc: false
```
### Advanced connectivity settings
```yaml
camunda:
client:
keep-alive: PT60S
override-authority: host:port
max-message-size: 4194304
max-metadata-size: 4194304
ca-certificate-path: path/to/certificate
request-timeout: PT10S
request-timeout-offset: PT1S
```
**Keep alive:** Time interval between keep alive messages sent to the gateway (default is 45s).
**Override authority:** The alternative authority to use, commonly in the form `host` or `host:port`.
**Max message size:** A custom `maxMessageSize` allows the client to receive larger or smaller responses from Zeebe. Technically, it specifies the `maxInboundMessageSize` of the gRPC channel (default 5MB).
**Max metadata size:** A custom `maxMetadataSize` allows the client to receive larger or smaller response headers from Camunda.
**CA certificate path:** Path to a root CA certificate to be used instead of the certificate in the default store.
**Request timeout:** The timeout for all requests sent to Camunda. There is an additional option to define the timeout for workers.
**Request timeout offset:** The offset being added to the timeout on asynchronous requests sent to Camunda to cover the network latency.
### Multi-tenancy
To connect the client to a specific tenant, you can configure:
```yaml
camunda:
client:
tenant-id: myTenant
```
This does also affect the default tenant being used by all job workers, however there are [more possibilities](#control-tenant-usage) to configure them.
## Authentication
The authentication method is determined by `camunda.client.auth.method`. If omitted, the client will try to detect the authentication method based on the provided properties.
Authenticate with the cluster using the following alternative methods:
:::info
When using `camunda.client.mode=saas`, the authentication method presets are not applied in favor of the properties contained in the SaaS preset.
:::
### No authentication
By default, no authentication will be used.
To explicitly activate this method, you can set:
```yaml
camunda:
client:
auth:
method: none
```
As alternative, do not provide any other property indicating an implicit authentication method.
This will load this preset:
```yaml reference referenceLinkText="Source" title="No authentication"
https://github.com/camunda/camunda/blob/main/clients/camunda-spring-boot-starter/src/main/resources/auth-methods/none.yaml
```
### Basic authentication
You can authenticate with the cluster using Basic authentication, if the cluster is setup to use Basic authentication.
To explicitly activate this method, you can set:
```yaml
camunda:
client:
auth:
method: basic
```
This authentication method will be implied if you set either `camunda.client.auth.username` or `camunda.client.auth.password`.
This will load this preset:
```yaml reference referenceLinkText="Source" title="Basic authentication"
https://github.com/camunda/camunda/blob/main/clients/camunda-spring-boot-starter/src/main/resources/auth-methods/basic.yaml
```
### OIDC authentication
You can authenticate with the cluster using OpenID Connect (OIDC) with client ID and client secret.
To explicitly activate this method, you can set:
```yaml
camunda:
client:
auth:
method: oidc
```
This authentication method will be implied if you set either `camunda.client.auth.client-id` or `camunda.client.auth.client-secret`.
This will load this preset:
```yaml reference referenceLinkText="Source" title="OIDC authentication"
https://github.com/camunda/camunda/blob/main/clients/camunda-spring-boot-starter/src/main/resources/auth-methods/oidc.yaml
```
:::note
There are three ways to define the token URL. They're prioritized as follows:
1. Provide the `camunda.client.auth.token-url`.
2. Provide the issuer's well-known configuration URL `camunda.client.auth.well-known-configuration-url`. This extracts the token URL from the `token_url` field in the loaded configuration.
3. Provide the issuer's URL `camunda.client.auth.issuer-url`. This generates the well-known configuration URL and extracts the token URL from the `token_url` field in the loaded configuration.
:::
#### Credentials cache path
You can define the credentials cache path of the zeebe client, the property contains directory path and file name:
```yaml
camunda:
client:
auth:
credentials-cache-path: /tmp/credentials
```
#### Custom identity provider security context
Several identity providers, such as Keycloak, support client X.509 authorizers as an alternative to client credentials flow.
As a prerequisite, ensure you have proper KeyStore and TrustStore configured, so that:
- Both the Spring Camunda application and identity provider share the same CA trust certificates.
- Both the Spring Camunda and identity provider own certificates signed by trusted CA.
- Your Spring Camunda application own certificate has proper `Distinguished Name` (DN), e.g.
`CN=My Camunda Client, OU=Camunda Users, O=Best Company, C=DE`.
- Your application DN registered in the identity provider client authorization details.
Once prerequisites are satisfied, your Spring Camunda application must be configured either via global SSL context, or
with an exclusive context which is documented below.
Refer to your identity provider documentation on how to configure X.509 authentication. For example, [Keycloak](https://www.keycloak.org/server/mutual-tls).
If you require configuring SSL context exclusively for your identity provider, you can use this set of properties:
```yaml
camunda:
client:
auth:
keystore-path: /path/to/keystore.p12
keystore-password: password
keystore-key-password: password
truststore-path: /path/to/truststore.jks
truststore-password: password
```
- **keystore-path**: Path to client's KeyStore; can be both in JKS or PKCS12 formats
- **keystore-password**: KeyStore password
- **keystore-key-password**: Key material password
- **truststore-path**: Path to client's TrustStore
- **truststore-password**: TrustStore password
When the properties are not specified, the default SSL context is applied. For example, if you configure an application with
`javax.net.ssl.*` or `spring.ssl.*`, the latter is applied. If both `camunda.client.auth.*` and either `javax.net.ssl.*`
or `spring.ssl.*` properties are defined, the `camunda.client.auth.*` takes precedence.
## Job worker configuration options
### Job type
You can configure the job type via the `JobWorker` annotation:
```java
@JobWorker(type = "foo")
public void handleJobFoo() {
// handles jobs of type 'foo'
}
```
If you don't specify the `type` attribute, the **method name** is used by default:
```java
@JobWorker
public void foo() {
// handles jobs of type 'foo'
}
```
As a third possibility, you can set a task type as property:
```yaml
camunda:
client:
worker:
override:
foo:
type: bar
```
As a fourth possibility, you can set a default task type as property:
```yaml
camunda:
client:
worker:
defaults:
type: foo
```
This is used for all workers that do **not** set a task type via the annotation or set a job type as individual worker property.
### Control variable fetching
A job worker can submit a list of variables when activating jobs to limit the amount of data being sent.
There are implicit and explicit ways to control the variable fetching. While the implicit ones come with the job worker function parameters, the explicit ones are listed here.
#### Provide a list of variables to fetch
You can specify that you only want to fetch some variables (instead of all) when executing a job, which can decrease load and improve performance:
```java
@JobWorker(type = "foo", fetchVariables={"variable1", "variable2"})
public void handleJobFoo(final JobClient client, final ActivatedJob job) {
String variable1 = (String)job.getVariablesAsMap().get("variable1");
System.out.println(variable1);
// ...
}
```
You can also override the variables to fetch in your properties:
```yml
camunda:
client:
worker:
override:
foo:
fetch-variables:
- variable1
- variable2
```
:::caution
Using the properties-defined way of fetching variables will override **all** other detection strategies.
:::
#### Prevent the variable filtering
You can force that all variables are loaded anyway:
```java
@JobWorker(type = "foo", fetchAllVariables = true)
public void handleJobFoo(final JobClient client, final ActivatedJob job, @Variable String variable1) {
}
```
You can also override the forced fetching of all variables in your properties:
```yml
camunda:
client:
worker:
override:
foo:
force-fetch-all-variables: true
```
### Define job worker function parameters
The method signature you use to define job worker functions will affect how variables are retrieved.
Unless stated otherwise, all specified methods for fetching variables will be combined into a single list of variables to retrieve.
#### `JobClient` parameter
The `JobClient` is also part of the native `JobHandler` functional interface:
```java
@JobWorker(type = "foo")
public void handleJobFoo(final JobClient jobClient) {
// ...
}
```
#### `ActivatedJob` parameter
The `ActivatedJob` is also part of the native `JobHandler` functional interface.
This will **prevent** the implicit variable fetching detection as you can retrieve variables in a programmatic way now:
```java
@JobWorker(type = "foo")
public void handleJobFoo(final ActivatedJob job) {
String variable1 = (String)job.getVariablesAsMap().get("variable1");
System.out.println(variable1);
// ...
}
```
:::note
Only explicit variable fetching will be effective on using the `ActivatedJob` as parameter.
:::
#### Using `@Variable`
By using the `@Variable` annotation, there is a shortcut to make variable retrieval simpler and only fetch certain variables, making them available as parameters:
```java
@JobWorker(type = "foo")
public void handleJobFoo(@Variable(name = "variable1") String variable1) {
System.out.println(variable1);
// ...
}
```
If you don't specify the `name` attribute on the annotation, the **method parameter name** is used as the variable name if you enabled the [`-parameters` compiler flag](/apis-tools/camunda-spring-boot-starter/getting-started.md#enable-the-java-compiler--parameters-flag) in the [getting started section](/apis-tools/camunda-spring-boot-starter/getting-started.md):
```java
@JobWorker(type = "foo")
public void handleJobFoo(final JobClient client, final ActivatedJob job, @Variable String variable1) {
System.out.println(variable1);
// ...
}
```
:::note
This will add the name of the variable to the joint list of variables to fetch.
:::
#### Using `@VariablesAsType`
You can also use your own class into which the process variables are mapped to (comparable to `getVariablesAsType()` in the [Java client API](/apis-tools/java-client/getting-started.md)). Therefore, use the `@VariablesAsType` annotation. In the example below, `MyProcessVariables` refers to your own class:
```java
@JobWorker(type = "foo")
public ProcessVariables handleFoo(@VariablesAsType MyProcessVariables variables) {
// do whatever you need to do
variables.getMyAttributeX();
variables.setMyAttributeY(42);
// return variables object if something has changed, so the changes are submitted to Zeebe
return variables;
}
```
:::note
This will add the names of the fields of the used type to the joint list of variables to fetch. Jackson's `@JsonProperty` annotation is respected.
:::
#### Using `@Document`
You can inject a `DocumentContext` by using the `@Document` annotation:
```java
@JobWorker
public void processDocument(@Document DocumentContext doc) {
List documents = doc.getDocuments();
// do what you need to do with the document entries
}
```
Each `DocumentEntry` grants you access to the `DocumentReferenceResponse` that contains the reference data to the document and the `DocumentLinkResponse` that contains a link to the document.
On top, you can directly retrieve the document content as `InputStream` or `byte[]`.
#### Using `@CustomHeaders`
You can use the `@CustomHeaders` annotation for a `Map` parameter to retrieve [custom headers](/components/concepts/job-workers.md) for a job:
```java
@JobWorker
public void handleFoo(@CustomHeaders Map headers) {
// do whatever you need to do
}
```
:::note
This will not have any effect on the variable fetching behavior.
:::
#### Using `@ProcessInstanceKey`, `@ElementInstanceKey`, `@JobKey`, `@ProcessDefinitionKey` and `@RootProcessInstanceKey`
You can use the `@ProcessInstanceKey`, `@ElementInstanceKey`, `@JobKey`, `@ProcessDefinitionKey` and `@RootProcessInstanceKey` annotation for a `String`, `long` or `Long` parameter to retrieve the according key for a job:
```java
@JobWorker
public void handleFoo(
@ProcessInstanceKey String processInstanceKey,
@ElementInstanceKey long elementInstanceKey,
@JobKey Long jobKey,
@ProcessDefinitionKey String processDefinitionKey,
@RootProcessInstanceKey long rootProcessInstanceKey) {
// do whatever you need to do
}
```
### Completing jobs
#### Auto-completing jobs
By default, the `autoComplete` attribute is set to `true` for any job worker.
In this case, the Spring integration will handle job completion for you:
```java
@JobWorker(type = "foo")
public void handleJobFoo(final ActivatedJob job) {
// do whatever you need to do
// no need to call client.newCompleteCommand()...
}
```
:::note
The code within the handler method needs to be synchronously executed, as the completion will be triggered right after the method has finished.
:::
##### Returning results
When using `autoComplete` you can return:
- a `Map` containing the process variables to set as result of the job
- a `String` containing a valid JSON object
- an `InputStream` streaming a valid JSON object
- an `Object` that will be serialized to a JSON object
```java
@JobWorker(type = "foo")
public Map handleJobFoo(final ActivatedJob job) {
// some work
if (successful) {
// some data is returned to be stored as process variable
return variablesMap;
} else {
// problem shall be indicated to the process:
throw new BpmnError("DOESNT_WORK", "This does not work because...");
}
}
```
##### Documents as job results
If you want to send a document as job result, you can do this by making a `DocumentContext` part of the response.
It can be part of a `Map`:
```java
@JobWorker
public Map sendDocumentAsResult() {
String resultDocumentContent = documentService.loadResult();
Map result = new HashMap<>();
result.put("resultDocument", DocumentContext.result()
.addDocument(
"result.json", b -> b.content(resultDocumentContent).contentType("application/json"))
.build());
return result;
}
```
It can also be part of an `Object`:
```java
public record DocumentResult(DocumentContext responseDocument) {}
@JobWorker
public DocumentResult sendDocumentAsResult() {
String resultDocumentContent = documentService.loadResult();
DocumentContext responseDocument = DocumentContext.result()
.addDocument(
"result.json", b -> b.content(resultDocumentContent).contentType("application/json"))
.build());
return new DocumentResult(responseDocument);
}
```
#### Programmatically completing jobs
Your job worker code can also complete the job itself. This gives you more control over when exactly you want to complete the job (for example, allowing the completion to be moved to reactive callbacks):
```java
@JobWorker(type = "foo", autoComplete = false)
public void handleJobFoo(final JobClient client, final ActivatedJob job) {
// do whatever you need to do
client.newCompleteCommand(job.getKey())
.send()
.exceptionally( throwable -> { throw new RuntimeException("Could not complete job " + job, throwable); });
}
```
You can also control auto-completion in your configuration.
**Globally:**
```yaml
camunda:
client:
worker:
defaults:
auto-complete: false
```
**Per worker:**
```yaml
camunda:
client:
worker:
override:
foo:
auto-complete: false
```
Ideally, you **don't** use blocking behavior like `send().join()`, as this is a blocking call to wait for the issued command to be executed on the workflow engine. While this is very straightforward to use and produces easy-to-read code, blocking code is limited in terms of scalability.
This is why the worker sample above shows a different pattern (using `exceptionally`). Often, you might want to use the `whenComplete` callback:
```java
send().whenComplete((result, exception) -> {})
```
This registers a callback to be executed when the command on the workflow engine was executed or resulted in an exception. This allows for parallelism. This is discussed in more detail in [this blog post about writing good workers for Camunda 8](https://blog.bernd-ruecker.com/writing-good-workers-for-camunda-cloud-61d322cad862).
:::note
When completing jobs programmatically, you must specify `autoComplete = false`. Otherwise, there is a race condition between your programmatic job completion and the Spring integration job completion, and this can lead to unpredictable results.
:::
### React to problems
#### Throw a `BpmnError`
If your code encounters a problem that should trigger a [BPMN error](/components/modeler/bpmn/error-events/error-events.md), throw a `BpmnError` and provide the error code defined in BPMN:
```java
@JobWorker(type = "foo")
public void handleJobFoo() {
// some work
if (businessError) {
// problem shall be indicated to the process:
throw CamundaError.bpmnError("ERROR_CODE", "Some explanation why this does not work");
// this is a static function that returns an instance of BpmnError
}
}
```
#### Fail jobs in a controlled way
Whenever you want a job to fail in a controlled way, you can throw a `JobError` and provide parameters like `variables`, `retries` and `retryBackoff`:
```java
@JobWorker(type = "foo")
public void handleJobFoo() {
try {
// some work
} catch(DynamicRetryException e) {
// problem shall be indicated to the process:
throw CamundaError.jobError("Error message", new ErrorVariables(), null, this::calculateRetryBackoff, e);
// this is a static function that returns an instance of JobError with a dynamic retry backoff
} catch(StaticRetryException e) {
// problem shall be indicated to the process:
throw CamundaError.jobError("Error message", new ErrorVariables(), null, Duration.ofSeconds(10), e);
// this is a static function that returns an instance of JobError with a static retry backoff
}
}
```
The JobError takes 5 parameters:
- `errorMessage`: String
- `variables`: Object _(optional)_, default `null`
- `retries`: Integer _(optional)_, defaults to `job.getRetries() - 1`
- `retryBackoff`: Duration _or_ Function (Integer -> Duration) _(optional)_, defaults to the configured retry backoff, function input are the retries that will be submitted
- `cause`: Exception _(optional)_, defaults to `null`
:::note
The job error is sent to the engine by the SDK calling the [Fail Job API](/apis-tools/orchestration-cluster-api-rest/specifications/fail-job.api.mdx). The stacktrace of the job error will become the actual error message. The provided cause will be visible in Operate.
:::
#### Implicitly failing jobs
If your handler method would throw any other exception than the ones listed above, the default Camunda Client error handling will apply, decrementing retries with a `retryBackoff` of 0.
### Configuring the job worker thread pool
The number of threads for invocation of job workers (default 1):
```yaml
camunda:
client:
execution-threads: 2
```
:::note
We generally do not advise using a thread pool for workers, but rather implement asynchronous code, see [writing good workers](/components/best-practices/development/writing-good-workers.md) for additional details.
:::
### Further job worker configuration options
#### Disable a job worker
You can disable workers via the `enabled` parameter of the `@JobWorker` annotation:
```java
@JobWorker(enabled = false)
public void foo() {
// worker's code - now disabled
}
```
You can also override this setting via your `application.yaml` file:
```yaml
camunda:
client:
worker:
override:
foo:
enabled: false
```
This is especially useful if you have a bigger code base including many workers, but want to start only some of them. Typical use cases are:
- Testing: You only want one specific worker to run at a time.
- Load balancing: You want to control which workers run on which instance of cluster nodes.
- Migration: There are two applications, and you want to migrate a worker from one to another. With this switch, you can disable workers via configuration in the old application once they are available within the new.
To disable all workers, but still have the Camunda client available, you can use:
```yaml
camunda:
client:
worker:
defaults:
enabled: false
```
#### Configure jobs in flight
Number of jobs for a worker that are polled from the broker to be worked on in this client:
```java
@JobWorker(maxJobsActive = 64)
public void foo() {
// worker's code
}
```
This can also be configured as property:
```yaml
camunda:
client:
worker:
override:
foo:
max-jobs-active: 64
```
To configure a global default, you can set:
```yaml
camunda:
client:
worker:
defaults:
max-jobs-active: 64
```
#### Enable job streaming
Read more about this feature in the [job streaming documentation](/apis-tools/java-client/job-worker.md#job-streaming).
Job streaming is disabled by default for job workers. To enable job streaming on the Camunda client, configure it as follows:
```java
@JobWorker(streamEnabled = true)
public void foo() {
// worker's code
}
```
This can also be configured as property:
```yaml
camunda:
client:
worker:
override:
foo:
stream-enabled: true
```
To configure a global default, you can set:
```yaml
camunda:
client:
worker:
defaults:
stream-enabled: true
```
#### Control tenant usage
Job workers can be configured to work on jobs from specific [tenants](#multi-tenancy) using either [specific tenant IDs](#filtering-by-provided-tenant-IDs) or the [assigned tenants in the engine](#filtering-by-assigned-tenants).
##### Filter by assigned tenants
You can configure a job worker to use the tenants assigned to it in the engine, rather than providing explicit tenant IDs. Use the `tenantFilter` annotation property with `TenantFilter.ASSIGNED`:
```java
@JobWorker(tenantFilter = TenantFilter.ASSIGNED)
public void foo() {
// worker's code
}
```
When `TenantFilter.ASSIGNED` is set, any `tenant-ids` configured via the annotation or YAML are ignored.
You can also override the tenant filter for a specific worker:
```yaml
camunda:
client:
worker:
override:
foo:
tenant-filter: ASSIGNED
```
To configure a global default:
```yaml
camunda:
client:
worker:
defaults:
tenant-filter: ASSIGNED
```
##### Filter by provided tenant IDs
The default behaviour is `TenantFilter.PROVIDED`, where the worker retrieves jobs for the tenant IDs explicitly configured. Configure global worker defaults for additional `tenant-ids` to be used by all workers:
```yaml
camunda:
client:
worker:
defaults:
tenant-ids:
-
- foo
```
Additionally, you can set `tenantIds` on the job worker level by using the annotation:
```java
@JobWorker(tenantIds="myOtherTenant")
public void foo() {
// worker's code
}
```
You can also override the `tenant-ids` for each worker:
```yaml
camunda:
client:
worker:
override:
foo:
tenants-ids:
-
- foo
```
#### Define the job timeout
To define the job timeout, you can set the annotation (`long` in milliseconds):
```java
@JobWorker(timeout=60000)
public void foo() {
// worker's code
}
```
Moreover, you can override the timeout for the worker (as ISO 8601 duration expression):
```yaml
camunda:
client:
worker:
override:
foo:
timeout: PT1M
```
You can also set a global default:
```yaml
camunda:
client:
worker:
defaults:
timeout: PT1M
```
#### Configure the retry backoff
If you want to apply a retry backoff that should be applied if a job fails without a job error, you can set the annotation (`long` in milliseconds):
```java
@JobWorker(retryBackoff=10000L)
public void work() {
// worker's code
}
```
Moreover, you can override the retry backoff for the worker (as ISO 8601 duration expression):
```yaml
camunda:
client:
worker:
override:
foo:
retry-backoff: PT10S
```
You can also set a global default:
```yaml
camunda:
client:
worker:
defaults:
retry-backoff: PT10S
```
## Deploy resources on start-up
To deploy process models on application start-up, use the `@Deployment` annotation:
```java
@Deployment(resources = "classpath:demoProcess.bpmn")
public class MyRandomBean {
// make sure this bean is registered
}
```
### Specify resources to deploy
This annotation uses the [Spring resource loader](https://docs.spring.io/springframework/reference/core/resources.html) and can deploy multiple files at once. For example:
```java
@Deployment(resources = {"classpath:demoProcess.bpmn" , "classpath:demoProcess2.bpmn"})
```
Or, define wildcard patterns:
```java
@Deployment(resources = "classpath*:/bpmn/**/*.bpmn")
```
The resource loader automatically searches the entire classpath, including dependency JARs. To deploy only the resources packaged with the annotated class, use:
```java
@Deployment(resources = "classpath*:/bpmn/**/*.bpmn", ownJarOnly = true)
```
You can also set this globally:
```yaml
camunda:
client:
deployment:
own-jar-only: true
```
### Specify the tenant to deploy to
To adjust the tenant to deploy to, set the `tenantId` property of the `@Deployment` annotation:
```java
@Deployment(resources = "classpath:demoProcess.bpmn", tenantId = "myTenant")
public class MyRandomBean {
// make sure this bean is registered
}
```
By default, the starter uses the `tenantId` from `camunda.client.tenant-id`.
### Disable deployment
To disable the deployment of annotations, you can set:
```yaml
camunda:
client:
deployment:
enabled: false
```
## React to events
The Camunda Spring Boot Starter integrates with Spring events and also publishes its own events.
### Camunda client lifecycle events
#### Camunda client created
To react when the Camunda client is created, add an event listener:
```java
@EventListener
public void onCamundaClientCreated(CamundaClientCreatedEvent event) {
// do what you need to do
}
```
#### Camunda client closing event
To react on the closing of the Camunda client, you can do this:
```java
@EventListener
public void onCamundaClientClosing(CamundaClientClosingEvent event) {
// do what you need to do
}
```
#### Lifecycle aware interface
To subscribe to the Camunda client lifecycle at once, you can also use an interface:
```java
@Component
public class CamundaLifecycleListener implements CamundaClientLifecycleAware {
@Override
public void onStart(CamundaClient client) {
// do what you need to do
}
@Override
public void onStop(CamundaClient client) {
// do what you need to do
}
}
```
### Post deployment event
To react on the creation of [deployments on start-up](#deploying-resources-on-start-up), you can do this:
```java
@EventListener
public void onDeploymentCreated(CamundaPostDeploymentEvent event) {
// do what you need to do
}
```
The event will grant you access to a list of deployments that have been created.
## Observe metrics
The Camunda Spring Boot Starter provides some out-of-the-box metrics that can be leveraged via [Spring Actuator](https://docs.spring.io/spring-boot/docs/current/actuator-api/htmlsingle/). Whenever actuator is on the classpath, you can access the following metrics:
- `camunda.job.invocations`: Number of invocations of job workers (tagging the job type)
For all of those metrics, the following actions are recorded:
- `activated`: The job was activated and started to process an item.
- `completed`: The processing was completed successfully.
- `failed`: The processing failed with some exception.
- `bpmn-error`: The processing completed by throwing a BPMN error (which means there was no technical problem).
In a default setup, you can enable metrics to be served via http:
```yaml
management:
endpoints:
web:
exposure:
include: metrics
```
Access them via [http://localhost:8080/actuator/metrics/](http://localhost:8080/actuator/metrics/).
---
## Camunda Spring Boot Starter
## About
The Camunda Spring Boot Starter is the official way to integrate Camunda 8 APIs ([gRPC](/apis-tools/zeebe-api/grpc.md) and [REST](/apis-tools/orchestration-cluster-api-rest/orchestration-cluster-api-rest-overview.md)) into your Spring Boot project. You can use it to orchestrate microservices, manage human tasks, and interact with process data using idiomatic Spring Boot patterns.
:::info Public API
The Camunda Spring Boot Starter is part of the Camunda 8 [public API](/reference/public-api.md) and follows [Semantic Versioning](https://semver.org/) (except for alpha features). Minor and patch releases will not introduce breaking changes.
:::
:::info Migration from Spring Zeebe SDK
**The Camunda Spring Boot Starter replaces the Spring Zeebe SDK as of version 8.8.**
- Uses the new Camunda Java Client under the hood
- REST is the default protocol (gRPC is configurable)
- Spring Zeebe SDK will be **removed in version 8.10**
- **Migrate before upgrading to 8.10** to avoid breaking changes
See the [migration guide](/reference/announcements-release-notes/880/880-announcements.md#camunda-java-client-and-camunda-spring-boot-starter) for details.
:::
## What you can build with it
With the Camunda Spring Boot Starter, you can build:
- **Job workers** that perform automated tasks and call external systems (APIs, databases, file systems)
- **Integration services** that connect Camunda processes with existing systems or third-party services
- **Data processing applications** that use process data for visualization, analytics, or business intelligence
## Version compatibility
| Camunda Spring Boot Starter artifact | Camunda Spring Boot Starter version | JDK | Bundled Spring Boot version | Compatible Spring Boot version(s) |
| ------------------------------------ | ----------------------------------- | ---- | --------------------------- | --------------------------------- |
| `camunda-spring-boot-starter` | 8.9.x | ≥ 17 | 4.0.x | |
| `camunda-spring-boot-4-starter` | 8.9.x | ≥ 17 | 4.0.x | |
| `camunda-spring-boot-3-starter` | 8.9.x | ≥ 17 | 3.5.x | |
### Dedicated Spring Boot 3 and 4 modules
Starting with Camunda 8.9, the default `camunda-spring-boot-starter` artifact is bundled with **Spring Boot 4.0.x**. Additionally, two dedicated modules are available:
- **`camunda-spring-boot-4-starter`**: Identical to `camunda-spring-boot-starter`. Use this if you want to explicitly target Spring Boot 4.0.x.
- **`camunda-spring-boot-3-starter`**: Bundled with Spring Boot 3.5.x. Use this if your application is not yet ready to upgrade to Spring Boot 4.0.
:::caution Spring Boot 3.5.x support window
OSS support for Spring Boot 3.5.x ends in June 2026 (see [Spring Boot support timeline](https://spring.io/projects/spring-boot#support)). We encourage you to migrate to Spring Boot 4.0 before the support window closes. If you need continued Spring Boot 3.x support after June 2026, consider obtaining enterprise support from a third-party provider.
:::
To use the Spring Boot 3 module, replace the default dependency in your project:
```xml
io.camundacamunda-spring-boot-3-starter8.9.x
```
## Get started
### Step 1: Add the dependency
Add the Camunda Spring Boot Starter to your project:
**Maven:**
```xml
io.camundacamunda-spring-boot-starter8.9.x
```
### Step 2: Enable the Java Compiler `-parameters` flag (optional)
If you want to use parameter names for process variables without specifying annotation values, enable the Java compiler flag `-parameters`.
**Maven:**
```xml
org.apache.maven.pluginsmaven-compiler-plugin-parameters
```
If you are using Gradle:
```xml
tasks.withType(JavaCompile) {
options.compilerArgs << '-parameters'
}
```
If you are using IntelliJ:
```agsl
Settings > Build, Execution, Deployment > Compiler > Java Compiler
```
### Step 3a: Configure the Orchestration Cluster connection for Self-Managed
Set up your connection and authentication in `application.yaml` as shown below. Choose the mode and authentication method for your environment.
Choose the authentication method and gRPC/REST address for your environment:
By default, no authentication will be used.
```yaml
camunda:
client:
mode: self-managed
auth:
method: none
grpc-address: https://my-grpc-address
rest-address: https://my-rest-address
```
To activate basic authentication:
```yaml
camunda:
client:
mode: self-managed
auth:
method: basic
username:
password:
grpc-address: https://my-grpc-address
rest-address: https://my-rest-address
```
If you set up a [Self-Managed cluster with OIDC](/self-managed/deployment/helm/configure/authentication-and-authorization/index.md), you must configure the accompanying client credentials:
```yaml
camunda:
client:
mode: self-managed
auth:
method: oidc
client-id:
client-secret:
issuer-url: http://localhost:18080/auth/realms/camunda-platform
audience:
scope:
grpc-address: https://my-grpc-address
rest-address: https://my-rest-address
```
:::note
Ensure all addresses use absolute URI format: `scheme://host(:port)`.
:::
**Notes for Microsoft Entra ID**
- Use `scope: CLIENT_ID_OC + "/.default"` instead of `scope: CLIENT_ID_OC`.
- The `issuer-url` is typically in the format:
```
https://login.microsoftonline.com//v2.0
```
:::note Audience validation
If you have [configured the audiences property for the Orchestration Cluster (`camunda.security.authentication.oidc.audiences`)](/self-managed/components/orchestration-cluster/core-settings/configuration/properties.md#camunda.security.authentication.oidc), the Orchestration Cluster will validate the audience claim in the token against the configured audiences.
Make sure your token includes the correct audience from the Orchestration Cluster configuration, or add your audience to the configuration. Often this is the client ID you used when setting up the Orchestration Cluster.
:::
### Step 3b: Configure the Orchestration Cluster connection for SaaS
Set up your connection and authentication in `application.yaml` as shown below:
```yaml
camunda:
client:
mode: saas
auth:
client-id:
client-secret:
cloud:
cluster-id:
region:
```
## Start building your process application
With your project configured, you are ready to build your process application. Below are the core operations you’ll typically perform, along with guidance on the next steps.
### Inject the Camunda client
You can inject the Camunda client and work with it to create new workflow instances, for example:
```java
@Autowired
private CamundaClient client;
```
## Implement the job worker
Declare a method like this on a bean:
```java
@JobWorker(type = "foo")
public void handleJobFoo() {
// do whatever you need to do
}
```
To learn about all options you have with job workers, check out the [configuration](./configuration.md#job-worker-configuration-options) page.
## Deploy process models
To deploy process models on application start-up, use the `@Deployment` annotation:
```java
@SpringBootApplication
@Deployment(resources = "classpath:demoProcess.bpmn")
public class MySpringBootApplication {
```
To learn about all options about the usage of the `@Deployment` annotation, check out the [configuration](./configuration.md#deploying-resources-on-start-up) page.
**Need help?**
- [Camunda Community Forum](https://forum.camunda.io/) – Get help from the community.
- [GitHub repository](https://github.com/camunda/camunda) – Report issues and contribute.
---
## Properties reference
Properties for the Camunda Spring Boot Starter.
## Properties
### `camunda.client`
Properties for the Camunda client.
Property
Description
Default value
The path to a root Certificate Authority (CA) certificate to use instead of the certificate in the default store.
Type: stringnull
Enable or disable the Camunda client. If disabled, the client bean is not created.
Type: booleantrue
The number of threads for invocation of job workers.
Type: integer1
The gRPC address of Camunda that the client can connect to. The address must be an absolute URL, including the scheme. An alternative default is set by both `camunda.client.mode`.
Type: url"http://0.0.0.0:26500"
The time interval between keep-alive messages sent to the gateway.
Type: durationnull
The maximum number of concurrent HTTP connections the client can open.
Type: integer100
A custom `maxMessageSize` sets the maximum inbound message size the client can receive from Camunda. It specifies the `maxInboundMessageSize` of the gRPC channel.
Type: dataSize"5MB"
A custom `maxMetadataSize` sets the maximum inbound metadata size the client can receive from Camunda. It specifies the `maxInboundMetadataSize` of the gRPC channel.
Type: dataSize"16KB"
The default time-to-live for a message when no value is provided.
Type: duration"PT1H"
The client mode to use. If not set, `saas` mode is detected based on the presence of a `camunda.client.cloud.cluster-id`.
Type: enum[self-managed, saas]null
Overrides the authority used with TLS virtual hosting to change hostname verification during the TLS handshake. It does not change the actual host connected to.
Type: stringnull
If `true`, prefers REST over gRPC for operations supported by both protocols.
Type: booleantrue
The request timeout to use when not overridden by a specific command.
Type: duration"PT10S"
The request timeout client offset applies to commands that also pass the request timeout to the server. It ensures the client timeout occurs after the server timeout. For these commands, the client-side timeout equals the request timeout plus the offset.
Type: duration"PT1S"
The REST API address of the Camunda instance that the client can connect to. The address must be an absolute URL, including the scheme. An alternative default is set by both `camunda.client.mode`.
Type: url"http://0.0.0.0:8080"
The tenant ID used for tenant-aware commands when no tenant ID is set.
Type: string"<default>"
If `true`, enables client-side load balancing by using DNS-based resolution and distributing requests across all resolved addresses. Useful for setups without an external load balancer, such as Docker Compose, Testcontainers, or Kubernetes headless services.
Type: booleanfalse
### `camunda.client.auth`
Properties for authenticating the Camunda client.
Property
Description
Default value
The resource for which the access token must be valid. A default is set by `camunda.client.mode: saas` and `camunda.client.auth.method: oidc`.
Type: stringnull
The client ID to use when requesting an access token from the OAuth authorization server.
Type: stringnull
The client secret to use when requesting an access token from the OAuth authorization server.
Type: stringnull
The connection timeout for requests to the OAuth credentials provider.
Type: duration"PT5S"
The path to the credentials cache file. If unset or empty, the OAuth provider caches credentials only in memory and does not persist them across restarts. Set this to a writable path to opt in to persistent file-based caching. See issue #13124.
Type: stringnull
The url of the issuer for the access token. It is used to generate the well-known configuration url from which the `token-url` is retrieved. Only applied if the `camunda.client.auth.well-known-configuration-url` is not set. A default is set by `camunda.client.auth.method: oidc`.
Type: urlnull
The keystore key password for the OAuth identity provider.
Type: stringnull
The keystore password for the OAuth identity provider.
Type: stringnull
The path to the keystore for the OAuth identity provider.
Type: filenull
The authentication method to use. If not set, it is detected based on the presence of a username, password, client ID, and client secret. A default is set by `camunda.client.mode: saas`.
Type: enum[none, basic, oidc]null
The password to be use for basic authentication. A default is set by `camunda.client.auth.method: basic`.
Type: stringnull
The lead time before actual token expiry at which a background refresh is triggered. The token is still considered valid inside this window; this is a policy knob for how early refresh kicks in so callers don't have to block on a synchronous refresh at the cliff edge. Must be strictly larger than the internal expiry grace period.
Type: durationnull
The data read timeout for requests to the OAuth credentials provider.
Type: duration"PT5S"
The resource for which the access token must be valid.
Type: stringnull
The scopes of the access token.
Type: stringnull
The multiplier applied to the backoff duration between successive token fetch retry attempts. Must be greater than or equal to 1.0.
Type: doublenull
The initial backoff duration applied between token fetch retry attempts. Subsequent delays grow geometrically by `token-fetch-backoff-multiplier`.
Type: durationnull
The maximum number of attempts (including the initial one) when fetching a token from the OAuth authorization server. Retries are only attempted on IOException or HTTP status codes configured via `token-fetch-retryable-status-codes`.
Type: integernull
Duration for which token fetches fail fast after the token endpoint returns a non-retryable response. After the cooldown elapses, the next call retries; if it fails again non-retryably, the latch re-arms with a new cooldown. Set to Duration.ZERO to disable the cooldown entirely.
Type: durationnull
The set of HTTP status codes from the token endpoint that should be retried with backoff. Any non-200 status code outside this set trips a non-retryable failure latch that fails fast for the duration of tokenFetchNonRetryableCooldown.
Type: array[integer]null
The authorization server URL from which to request the access token. A default is set by `camunda.client.mode: saas`.
Type: urlnull
The truststore password for the OAuth identity provider.
Type: stringnull
The path to the truststore for the OAuth identity provider.
Type: filenull
The username to use for basic authentication. A default is set by `camunda.client.auth.method: basic`.
Type: stringnull
The url of the well-known configuration of the issuer. It is used to retrieve the `token-url`. Only applied if `camunda.client.auth.token-url` is not set.
Type: urlnull
### `camunda.client.auth.client-assertion`
Properties for OIDC authentication using a client assertion instead of a client secret.
Property
Description
Default value
The alias of the key containing the certificate used to sign the client assertion certificate. If not set, the first alias from the keystore is used.
Type: stringnull
The password of the key referenced by the alias. If not set, the keystore password is used.
Type: stringnull
The password of the referenced keystore.
Type: stringnull
The path to the keystore where the client assertion certificate is stored.
Type: filenull
### `camunda.client.cloud`
Properties for connecting the Camunda client to SaaS. These are used to compose default connection details when the client is configured to `camunda.client.mode: saas`.
Property
Description
Default value
The cluster ID the Camunda client connects to.
Type: stringnull
The domain the Camunda client connects to. Change this to connect to a non-production instance of Camunda Cloud.
Type: stringnull
The port the Camunda client connects to.
Type: integernull
The region the Camunda client connects to.
Type: stringnull
### `camunda.client.cluster-variables`
Properties for setting cluster variables at startup.
Property
Description
Default value
Indicates if the `@ClusterVariables` annotation is processed and configured variables are applied.
Type: booleantrue
Cluster variables to set at startup as key-value pairs.
Type: map[string,object]null
### `camunda.client.deployment`
Properties for automatic deployment at startup.
Property
Description
Default value
Indicates if the `@Deployment` annotation is processed.
Type: booleantrue
Indicates if the resources selected by the deployment annotation have to reside in the same jar as the annotated class. This property acts as the default behavior. If the `@Deployment` annotation explicitly sets its `ownJarOnly` parameter, that annotation-level value overrides this property for the annotated deployment.
Type: booleanfalse
### `camunda.client.worker.defaults`
Global default properties for job workers registered to the Camunda client.
Property
Description
Default value
Enable or disable automatic job completion after method invocation.
Type: booleantrue
Enable or disable the job worker.
Type: booleantrue
List of variable names to fetch on job activation. When set in defaults, it extends the list of variables to fetch from the annotation. When set in an override, it replaces the list of variables to fetch.
Type: array[string]null
Sets whether all variables are fetched. Overrides `fetch-variables`.
Type: booleanfalse
The maximum number of jobs exclusively activated for this worker at the same time.
Type: integer32
The maximum number of retries before automatic responses (complete, fail, bpmn error) for jobs are no longer attempted.
Type: integer0
The name of the worker owner. If set to default, it is generated as `${beanName}#${methodName}`.
Type: string"default"
The maximal interval between polls for new jobs.
Type: duration"PT0.1S"
The request timeout for the activate job request used to poll for new jobs.
Type: duration"PT10S"
The backoff before a retry of a failed job is possible.
Type: duration"PT0S"
Opt-in feature flag that enables job streaming. When enabled, the job worker uses both streaming and polling to activate jobs. A long-lived stream eagerly pushes new jobs, and polling retrieves jobs created before any streams were opened.
Type: booleanfalse
If streaming is enabled, sets the maximum duration the worker will wait without receiving any job on the open stream before cancelling and recreating it. The timer is reset every time a job is received. Must be strictly less than `stream-timeout` when both are set.
Type: duration"PT10M"
If streaming is enabled, sets the maximum lifetime for a stream. When this timeout is reached, the stream closes, and no more jobs are activated or received. If the worker is still open, a new stream opens immediately.
Type: duration"PT8H"
Sets the tenant filter for the job worker, which determines how the worker considers tenant IDs when activating jobs.
Type: enum[assigned, provided]"PROVIDED"
Sets the tenants for which the job worker is registered. When set in defaults, it extends the list of tenant IDs from the annotation. When set in override, it replaces the list of tenant IDs.
Type: array[string]["<default>"]
The time a job remains exclusively assigned to the worker.
Type: duration"PT5M"
The type of jobs to work on.
Type: stringnull
### `management.endpoint.jobworkers`
Properties for configuring the `jobworkers` management endpoint.
Property
Description
Default value
Permitted level of access for the jobworkers endpoint.
Type: enum[none, read_only, unrestricted]"unrestricted"
Maximum time that a response can be cached.
Type: duration"0ms"
### `camunda.client.worker.override`
Properties for overriding settings of individual job workers registered to the Camunda client. The key of the override is the job type or worker name.
Property
Description
Default value
Enable or disable automatic job completion after method invocation.
Type: booleannull
Enable or disable the job worker.
Type: booleannull
List of variable names to fetch on job activation. When set in defaults, it extends the list of variables to fetch from the annotation. When set in an override, it replaces the list of variables to fetch.
Type: array[string]null
Sets whether all variables are fetched. Overrides `fetch-variables`.
Type: booleannull
The maximum number of jobs exclusively activated for this worker at the same time.
Type: integernull
The maximum number of retries before automatic responses (complete, fail, bpmn error) for jobs are no longer attempted.
Type: integernull
The name of the worker owner. If set to default, it is generated as `${beanName}#${methodName}`.
Type: stringnull
The maximal interval between polls for new jobs.
Type: durationnull
The request timeout for the activate job request used to poll for new jobs.
Type: durationnull
The backoff before a retry of a failed job is possible.
Type: durationnull
Opt-in feature flag that enables job streaming. When enabled, the job worker uses both streaming and polling to activate jobs. A long-lived stream eagerly pushes new jobs, and polling retrieves jobs created before any streams were opened.
Type: booleannull
If streaming is enabled, sets the maximum duration the worker will wait without receiving any job on the open stream before cancelling and recreating it. The timer is reset every time a job is received. Must be strictly less than `stream-timeout` when both are set.
Type: durationnull
If streaming is enabled, sets the maximum lifetime for a stream. When this timeout is reached, the stream closes, and no more jobs are activated or received. If the worker is still open, a new stream opens immediately.
Type: durationnull
Sets the tenant filter for the job worker, which determines how the worker considers tenant IDs when activating jobs.
Type: enum[assigned, provided]null
Sets the tenants for which the job worker is registered. When set in defaults, it extends the list of tenant IDs from the annotation. When set in override, it replaces the list of tenant IDs.
Type: array[string]null
The time a job remains exclusively assigned to the worker.
Type: durationnull
The type of jobs to work on.
Type: stringnull
## Deprecated properties
:::caution
The following properties are deprecated. See the replacement property and related hints.
The deprecated properties are still effective if their replacement is not used yet. The SDK hints on the usage of deprecated properties by logging warn statements during startup.
:::
### `camunda.client`
Deprecated properties for the Camunda client.
Property
Replacement
Hint
N/A
N/A
N/A
### `camunda.client.auth`
Deprecated properties for authenticating the Camunda client.
Property
Replacement
Hint
N/A
### `camunda.client.cloud`
Deprecated properties for connecting the Camunda client to SaaS. These are used to compose default connection details when the client is configured to `camunda.client.mode: saas`.
Property
Replacement
Hint
N/A
### `camunda.client.identity`
Deprecated properties for identity settings.
Property
Replacement
Hint
Identity is now part of Camunda.
Identity is now part of Camunda.
Identity is now part of Camunda.
Identity is now part of Camunda.
### `camunda.client.zeebe`
Deprecated properties for Zeebe client settings.
Property
Replacement
Hint
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
### `camunda.client.zeebe.defaults`
Deprecated default properties for Zeebe job workers.
Property
Replacement
Hint
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
N/A
### `camunda.client.zeebe.deployment`
Deprecated deployment properties for Zeebe.
Property
Replacement
Hint
N/A
### `camunda.client.zeebe.override`
Deprecated properties for overriding individual job workers registered to the Camunda client. Replaced by `camunda.client.worker.override`.
### `common`
Deprecated common client properties.
Property
Replacement
Hint
N/A
N/A
N/A
N/A
N/A
N/A
The REST address is the unified endpoint for all interaction with Camunda.
N/A
### `common.keycloak`
Deprecated Keycloak-specific properties.
Property
Replacement
Hint
There is no keycloak-specific configuration for Camunda; the issuer is provided as a URL.
There is no keycloak-specific configuration for Camunda; the issuer is provided as a URL.
There is no keycloak-specific configuration for Camunda; the issuer is provided as a URL.
### `zeebe.client`
Deprecated Zeebe client properties.
Property
Replacement
Hint
Only the environment variables belonging to the Spring SDK are applied.
Client modes are now available.
N/A
N/A
N/A
N/A
N/A
### `zeebe.client.broker`
Deprecated Zeebe broker properties.
Property
Replacement
Hint
N/A
N/A
N/A
N/A
### `zeebe.client.cloud`
Deprecated Zeebe cloud connection properties.
Property
Replacement
Hint
N/A
N/A
N/A
N/A
N/A
N/A
The Zeebe client URL is now configured as HTTP/HTTPS URL.
N/A
N/A
### `zeebe.client.job`
Deprecated Zeebe job worker properties.
Property
Replacement
Hint
N/A
N/A
### `zeebe.client.message`
Deprecated Zeebe message properties.
Property
Replacement
Hint
N/A
N/A
### `zeebe.client.security`
Deprecated Zeebe security properties.
Property
Replacement
Hint
N/A
N/A
plaintext is now determined by the URL protocol (HTTP or HTTPS).
### `zeebe.client.worker`
Deprecated Zeebe job worker properties.
Property
Replacement
Hint
N/A
N/A
N/A
N/A
### `zeebe.client.worker.override`
Deprecated properties to override the individual job workers registered with the Camunda client. Replaced by `camunda.client.worker.override`.
---
## Community-supported component clients
:::note
Camunda extensions found in the [Camunda Community Hub](https://github.com/camunda-community-hub) are maintained by the community and are not part of the commercial Camunda product. Camunda does not support community extensions as part of its commercial services to enterprise customers. Please evaluate each client to make sure it meets your requirements before using.
:::
:::tip
Camunda now officially supports the [TypeScript SDK](/apis-tools/typescript/typescript-sdk.md) and the [Camunda Spring Boot Starter](/apis-tools/camunda-spring-boot-starter/getting-started.md).
:::
In addition to the core Camunda-maintained clients, there are a number of community-maintained component libraries:
- [Ballerina](https://github.com/camunda-community-hub/ballerina-zeebe)
- [C#](https://github.com/camunda-community-hub/zeebe-client-csharp)
- [CLI](https://github.com/camunda-community-hub/zeebe-client-go/blob/main/cmd/zbctl/zbctl.md)
- [Delphi](https://github.com/camunda-community-hub/DelphiZeeBeClient)
- [EJB](https://github.com/camunda-community-hub/zeebe-ejb-client)
- [Go](https://github.com/camunda-community-hub/zeebe-client-go)
- [Micronaut](https://github.com/camunda-community-hub/micronaut-zeebe-client)
- [Python](https://gitlab.com/stephane.ludwig/zeebe_python_grpc)
- [Quarkus](https://github.com/quarkiverse/quarkus-zeebe)
- [Ruby](https://github.com/zeebe-io/zeebe-client-ruby)
- [Rust](https://github.com/camunda-community-hub/zeebest)
- [.NET](https://github.com/camunda-community-hub/dotnet-custom-tasklist)
- [Java](https://github.com/camunda-community-hub/camunda-tasklist-client-java)
- [Java](https://github.com/camunda-community-hub/camunda-operate-client-java)
- [Web Modeler - Java](https://github.com/camunda-community-hub/web-modeler-java-client)
- [Console - Go](https://github.com/camunda-community-hub/console-customer-api-go)
---
## CamundaClient
:::caution Technical Preview
The C# SDK is a **technical preview** available from Camunda 8.9. It will become fully supported in Camunda 8.10. Its API surface may change in future releases without following semver.
:::
## Creating a Client
Factory method for creating CamundaClient instances.
```csharp
public static CamundaClient CreateClient(CamundaOptions? options = null)
```
Create a new CamundaClient.
| Parameter | Type | Description |
| --------- | ---------------- | ----------- |
| `options` | `CamundaOptions` | |
## Dependency Injection
Extension methods for registering in an .
### AddCamundaClient(IServiceCollection)
```csharp
public static IServiceCollection AddCamundaClient(this IServiceCollection services)
```
Registers a singleton using zero-config (environment variables only).
| Parameter | Type | Description |
| ---------- | -------------------- | ----------- |
| `services` | `IServiceCollection` | |
### AddCamundaClient(IServiceCollection, IConfiguration)
```csharp
public static IServiceCollection AddCamundaClient(this IServiceCollection services, IConfiguration configurationSection)
```
Registers a singleton using an section.
Typically called as services.AddCamundaClient(configuration.GetSection("Camunda")).
PascalCase keys in the section are mapped to canonical CAMUNDA\_\* env-var names internally.
Environment variables still apply as a base layer; section values override them.
| Parameter | Type | Description |
| ---------------------- | -------------------- | ----------- |
| `services` | `IServiceCollection` | |
| `configurationSection` | `IConfiguration` | |
### AddCamundaClient(IServiceCollection, Action)
```csharp
public static IServiceCollection AddCamundaClient(this IServiceCollection services, Action configure)
```
Registers a singleton with an options callback for full control.
| Parameter | Type | Description |
| ----------- | ------------------------ | ----------- |
| `services` | `IServiceCollection` | |
| `configure` | `Action` | |
## Overview
Primary Camunda client. Provides typed methods for all Camunda 8 REST API operations.
Auto-generated operation methods are added in the Generated/ partial class files.
This class provides the infrastructure: configuration, auth, retry, backpressure.
```csharp
public class CamundaClient : IDisposable, IAsyncDisposable
```
## Constructor
```csharp
public CamundaClient(CamundaOptions? options = null)
```
Create a new CamundaClient with the given options.
| Parameter | Type | Description |
| --------- | ---------------- | ----------- |
| `options` | `CamundaOptions` | |
## Properties
| Property | Type | Description |
| -------- | --------------- | ----------------------------------------------- |
| `Config` | `CamundaConfig` | The current hydrated configuration (read-only). |
## Methods
### Other
#### Create(CamundaOptions?)
```csharp
public static CamundaClient Create(CamundaOptions? options = null)
```
Create a new CamundaClient.
| Parameter | Type | Description |
| --------- | ---------------- | ----------- |
| `options` | `CamundaOptions` | |
**Returns:** `CamundaClient`
#### Dispose()
```csharp
public void Dispose()
```
Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
#### DisposeAsync()
```csharp
public ValueTask DisposeAsync()
```
Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously.
**Returns:** `ValueTask` — A task that represents the asynchronous dispose operation.
#### CreateAdminUserAsync(UserRequest, CancellationToken)
```csharp
public Task CreateAdminUserAsync(UserRequest body, CancellationToken ct = default)
```
Create admin user
Creates a new user and assigns the admin role to it. This endpoint is only usable when users are managed in the Orchestration Cluster and while no user is assigned to the admin role.
| Parameter | Type | Description |
| --------- | ------------------- | ----------- |
| `body` | `UserRequest` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task CreateAdminUserExample(Username username)
{
using var client = CamundaClient.Create();
var result = await client.CreateAdminUserAsync(new UserRequest
{
Username = username,
Name = "Admin User",
Email = "admin@example.com",
Password = "admin-password",
});
Console.WriteLine($"Admin user key: {result.Username}");
}
```
#### CreateAgentInstanceAsync(AgentInstanceCreationRequest, CancellationToken)
```csharp
public Task CreateAgentInstanceAsync(AgentInstanceCreationRequest body, CancellationToken ct = default)
```
Create agent instance
Creates a new agent instance. The returned key identifies the instance and must
be used in subsequent update and query calls.
| Parameter | Type | Description |
| --------- | ------------------------------ | ----------- |
| `body` | `AgentInstanceCreationRequest` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task CreateAgentInstanceExample(ElementInstanceKey elementInstanceKey)
{
using var client = CamundaClient.Create();
var result = await client.CreateAgentInstanceAsync(new AgentInstanceCreationRequest
{
ElementInstanceKey = elementInstanceKey,
Definition = new AgentInstanceDefinition
{
Model = "gpt-4o",
Provider = "openai",
SystemPrompt = "You are a helpful assistant.",
},
});
Console.WriteLine($"Created agent instance: {result.AgentInstanceKey}");
}
```
#### CreateGlobalTaskListenerAsync(CreateGlobalTaskListenerRequest, CancellationToken)
```csharp
public Task CreateGlobalTaskListenerAsync(CreateGlobalTaskListenerRequest body, CancellationToken ct = default)
```
Create global user task listener
Create a new global user task listener.
| Parameter | Type | Description |
| --------- | --------------------------------- | ----------- |
| `body` | `CreateGlobalTaskListenerRequest` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task CreateGlobalTaskListenerExample(GlobalListenerId id)
{
using var client = CamundaClient.Create();
var result = await client.CreateGlobalTaskListenerAsync(
new CreateGlobalTaskListenerRequest
{
EventTypes = new List { GlobalTaskListenerEventTypeEnum.Completing },
Id = id,
});
Console.WriteLine($"Task listener: {result.Id}");
}
```
#### CreateUserAsync(UserRequest, CancellationToken)
```csharp
public Task CreateUserAsync(UserRequest body, CancellationToken ct = default)
```
Create user
Create a new user.
| Parameter | Type | Description |
| --------- | ------------------- | ----------- |
| `body` | `UserRequest` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task CreateUserExample(Username username)
{
using var client = CamundaClient.Create();
var result = await client.CreateUserAsync(new UserRequest
{
Username = username,
Name = "Jane Doe",
Email = "jdoe@example.com",
Password = "secure-password",
});
Console.WriteLine($"User key: {result.Username}");
}
```
#### DeleteGlobalTaskListenerAsync(GlobalListenerId, CancellationToken)
```csharp
public Task DeleteGlobalTaskListenerAsync(GlobalListenerId id, CancellationToken ct = default)
```
Delete global user task listener
Deletes a global user task listener.
| Parameter | Type | Description |
| --------- | ------------------- | ----------- |
| `id` | `GlobalListenerId` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task DeleteGlobalTaskListenerExample(GlobalListenerId globalListenerId)
{
using var client = CamundaClient.Create();
await client.DeleteGlobalTaskListenerAsync(
globalListenerId);
}
```
#### DeleteUserAsync(Username, CancellationToken)
```csharp
public Task DeleteUserAsync(Username username, CancellationToken ct = default)
```
Delete user
Deletes a user.
| Parameter | Type | Description |
| ---------- | ------------------- | ----------- |
| `username` | `Username` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task DeleteUserExample(Username username)
{
using var client = CamundaClient.Create();
await client.DeleteUserAsync(username);
}
```
#### EvaluateConditionalsAsync(ConditionalEvaluationInstruction, CancellationToken)
```csharp
public Task EvaluateConditionalsAsync(ConditionalEvaluationInstruction body, CancellationToken ct = default)
```
Evaluate root level conditional start events
Evaluates root-level conditional start events for process definitions.
If the evaluation is successful, it will return the keys of all created process instances, along with their associated process definition key.
Multiple root-level conditional start events of the same process definition can trigger if their conditions evaluate to true.
| Parameter | Type | Description |
| --------- | ---------------------------------- | ----------- |
| `body` | `ConditionalEvaluationInstruction` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task EvaluateConditionalsExample()
{
using var client = CamundaClient.Create();
var result = await client.EvaluateConditionalsAsync(
new ConditionalEvaluationInstruction());
Console.WriteLine($"Result: {result}");
}
```
#### EvaluateExpressionAsync(ExpressionEvaluationRequest, CancellationToken)
```csharp
public Task EvaluateExpressionAsync(ExpressionEvaluationRequest body, CancellationToken ct = default)
```
Evaluate an expression
Evaluates a FEEL expression and returns the result. Supports references to tenant scoped cluster variables when a tenant ID is provided.
| Parameter | Type | Description |
| --------- | ----------------------------- | ----------- |
| `body` | `ExpressionEvaluationRequest` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task EvaluateExpressionExample()
{
using var client = CamundaClient.Create();
var result = await client.EvaluateExpressionAsync(
new ExpressionEvaluationRequest
{
Expression = "= 1 + 2",
});
Console.WriteLine($"Result: {result.Result}");
}
```
#### GetAgentInstanceAsync(AgentInstanceKey, ConsistencyOptions?, CancellationToken)
```csharp
public Task GetAgentInstanceAsync(AgentInstanceKey agentInstanceKey, ConsistencyOptions? consistency = null, CancellationToken ct = default)
```
Get agent instance
Returns agent instance as JSON.
| Parameter | Type | Description |
| ------------------ | ----------------------------------------- | ----------- |
| `agentInstanceKey` | `AgentInstanceKey` | |
| `consistency` | `ConsistencyOptions` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task GetAgentInstanceExample(AgentInstanceKey agentInstanceKey)
{
using var client = CamundaClient.Create();
var result = await client.GetAgentInstanceAsync(agentInstanceKey);
Console.WriteLine($"Agent instance: {result.AgentInstanceKey}, status: {result.Status}");
}
```
#### GetFormByKeyAsync(FormKey, ConsistencyOptions?, CancellationToken)
```csharp
public Task GetFormByKeyAsync(FormKey formKey, ConsistencyOptions? consistency = null, CancellationToken ct = default)
```
Get form by key
Get a form by its unique form key.
| Parameter | Type | Description |
| ------------- | -------------------------------- | ----------- |
| `formKey` | `FormKey` | |
| `consistency` | `ConsistencyOptions` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task GetFormByKeyExample(FormKey formKey)
{
using var client = CamundaClient.Create();
var result = await client.GetFormByKeyAsync(formKey);
Console.WriteLine($"Form: {result.FormId}, version: {result.Version}");
}
```
#### GetGlobalTaskListenerAsync(GlobalListenerId, ConsistencyOptions?, CancellationToken)
```csharp
public Task GetGlobalTaskListenerAsync(GlobalListenerId id, ConsistencyOptions? consistency = null, CancellationToken ct = default)
```
Get global user task listener
Get a global user task listener by its id.
| Parameter | Type | Description |
| ------------- | ---------------------------------------------- | ----------- |
| `id` | `GlobalListenerId` | |
| `consistency` | `ConsistencyOptions` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task GetGlobalTaskListenerExample(GlobalListenerId globalListenerId)
{
using var client = CamundaClient.Create();
var result = await client.GetGlobalTaskListenerAsync(
globalListenerId);
Console.WriteLine($"Task listener: {result.EventTypes}");
}
```
#### GetStatusAsync(CancellationToken)
```csharp
public Task GetStatusAsync(CancellationToken ct = default)
```
Get cluster status
Checks the health status of the cluster by verifying if there's at least one partition with a healthy leader.
| Parameter | Type | Description |
| --------- | ------------------- | ----------- |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task GetStatusExample()
{
using var client = CamundaClient.Create();
await client.GetStatusAsync();
Console.WriteLine("Cluster is healthy");
}
```
#### GetSystemConfigurationAsync(CancellationToken)
```csharp
public Task GetSystemConfigurationAsync(CancellationToken ct = default)
```
System configuration (alpha)
Returns the current system configuration. The response is an envelope
that groups settings by feature area.
This endpoint is an alpha feature and may be subject to change
in future releases.
| Parameter | Type | Description |
| --------- | ------------------- | ----------- |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task GetSystemConfigurationExample()
{
using var client = CamundaClient.Create();
var result = await client.GetSystemConfigurationAsync();
Console.WriteLine($"System config: {result}");
}
```
#### GetUserAsync(Username, ConsistencyOptions?, CancellationToken)
```csharp
public Task GetUserAsync(Username username, ConsistencyOptions? consistency = null, CancellationToken ct = default)
```
Get user
Get a user by its username.
| Parameter | Type | Description |
| ------------- | -------------------------------- | ----------- |
| `username` | `Username` | |
| `consistency` | `ConsistencyOptions` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
#### SearchAgentInstancesAsync(AgentInstanceSearchQuery, ConsistencyOptions?, CancellationToken)
```csharp
public Task SearchAgentInstancesAsync(AgentInstanceSearchQuery body, ConsistencyOptions? consistency = null, CancellationToken ct = default)
```
Search agent instances
Search for agent instances based on given criteria.
| Parameter | Type | Description |
| ------------- | ---------------------------------------------------- | ----------- |
| `body` | `AgentInstanceSearchQuery` | |
| `consistency` | `ConsistencyOptions` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task SearchAgentInstancesExample()
{
using var client = CamundaClient.Create();
var result = await client.SearchAgentInstancesAsync(new AgentInstanceSearchQuery());
foreach (var instance in result.Items)
{
Console.WriteLine($"Agent instance: {instance.AgentInstanceKey}, status: {instance.Status}");
}
}
```
#### SearchGlobalTaskListenersAsync(GlobalTaskListenerSearchQueryRequest, ConsistencyOptions?, CancellationToken)
```csharp
public Task SearchGlobalTaskListenersAsync(GlobalTaskListenerSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default)
```
Search global user task listeners
Search for global user task listeners based on given criteria.
| Parameter | Type | Description |
| ------------- | --------------------------------------------------------- | ----------- |
| `body` | `GlobalTaskListenerSearchQueryRequest` | |
| `consistency` | `ConsistencyOptions` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task SearchGlobalTaskListenersExample()
{
using var client = CamundaClient.Create();
var result = await client.SearchGlobalTaskListenersAsync(
new GlobalTaskListenerSearchQueryRequest());
foreach (var listener in result.Items)
{
Console.WriteLine($"Listener: {listener.Id}");
}
}
```
#### SearchUsersAsync(UserSearchQueryRequest, ConsistencyOptions?, CancellationToken)
```csharp
public Task SearchUsersAsync(UserSearchQueryRequest body, ConsistencyOptions? consistency = null, CancellationToken ct = default)
```
Search users
Search for users based on given criteria.
| Parameter | Type | Description |
| ------------- | -------------------------------------- | ----------- |
| `body` | `UserSearchQueryRequest` | |
| `consistency` | `ConsistencyOptions` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
#### UpdateAgentInstanceAsync(AgentInstanceKey, AgentInstanceUpdateRequest, CancellationToken)
```csharp
public Task UpdateAgentInstanceAsync(AgentInstanceKey agentInstanceKey, AgentInstanceUpdateRequest body, CancellationToken ct = default)
```
Update agent instance
Updates the mutable fields of an agent instance: status, metric counters, and
tools. Metric values are treated as deltas and applied immediately to the
aggregate counters. Tool updates replace the existing tool list. At least one of
status, metrics, or tools must be provided.
| Parameter | Type | Description |
| ------------------ | ---------------------------- | ----------- |
| `agentInstanceKey` | `AgentInstanceKey` | |
| `body` | `AgentInstanceUpdateRequest` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task UpdateAgentInstanceExample(AgentInstanceKey agentInstanceKey)
{
using var client = CamundaClient.Create();
await client.UpdateAgentInstanceAsync(
agentInstanceKey,
new AgentInstanceUpdateRequest
{
Status = AgentInstanceStatusEnum.THINKING,
Metrics = new AgentInstanceMetricsDelta
{
InputTokens = 150,
OutputTokens = 50,
ModelCalls = 1,
},
});
Console.WriteLine($"Updated agent instance: {agentInstanceKey}");
}
```
#### UpdateGlobalTaskListenerAsync(GlobalListenerId, UpdateGlobalTaskListenerRequest, CancellationToken)
```csharp
public Task UpdateGlobalTaskListenerAsync(GlobalListenerId id, UpdateGlobalTaskListenerRequest body, CancellationToken ct = default)
```
Update global user task listener
Updates a global user task listener.
| Parameter | Type | Description |
| --------- | --------------------------------- | ----------- |
| `id` | `GlobalListenerId` | |
| `body` | `UpdateGlobalTaskListenerRequest` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task UpdateGlobalTaskListenerExample(GlobalListenerId globalListenerId)
{
using var client = CamundaClient.Create();
var result = await client.UpdateGlobalTaskListenerAsync(
globalListenerId,
new UpdateGlobalTaskListenerRequest
{
EventTypes = new List { GlobalTaskListenerEventTypeEnum.Completing },
Type = "updated-task-listener",
});
Console.WriteLine($"Updated listener: {result.Id}");
}
```
#### UpdateUserAsync(Username, UserUpdateRequest, CancellationToken)
```csharp
public Task UpdateUserAsync(Username username, UserUpdateRequest body, CancellationToken ct = default)
```
Update user
Updates a user.
| Parameter | Type | Description |
| ---------- | ------------------- | ----------- |
| `username` | `Username` | |
| `body` | `UserUpdateRequest` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task UpdateUserExample(Username username)
{
using var client = CamundaClient.Create();
await client.UpdateUserAsync(
username,
new UserUpdateRequest
{
Name = "Jane Smith",
Email = "jsmith@example.com",
});
}
```
### Cluster
#### GetBackpressureState()
```csharp
public BackpressureState GetBackpressureState()
```
Current backpressure state snapshot.
**Returns:** `BackpressureState`
**Example**
```csharp
public static void GetBackpressureStateExample()
{
using var client = CamundaClient.Create();
var state = client.GetBackpressureState();
Console.WriteLine($"Severity: {state.Severity}, Permits: {state.PermitsMax}");
}
```
#### GetAuthenticationAsync(CancellationToken)
```csharp
public Task GetAuthenticationAsync(CancellationToken ct = default)
```
Get current user
Retrieves the current authenticated user.
| Parameter | Type | Description |
| --------- | ------------------- | ----------- |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task GetAuthenticationExample()
{
using var client = CamundaClient.Create();
var result = await client.GetAuthenticationAsync();
Console.WriteLine($"Authenticated user: {result.Username}");
}
```
#### GetLicenseAsync(CancellationToken)
```csharp
public Task GetLicenseAsync(CancellationToken ct = default)
```
Get license status
Obtains the status of the current Camunda license.
| Parameter | Type | Description |
| --------- | ------------------- | ----------- |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task GetLicenseExample()
{
using var client = CamundaClient.Create();
var result = await client.GetLicenseAsync();
Console.WriteLine($"License type: {result.LicenseType}");
}
```
#### GetTopologyAsync(CancellationToken)
```csharp
public Task GetTopologyAsync(CancellationToken ct = default)
```
Get cluster topology
Obtains the current topology of the cluster the gateway is part of.
| Parameter | Type | Description |
| --------- | ------------------- | ----------- |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task GetTopologyExample()
{
using var client = CamundaClient.Create();
var topology = await client.GetTopologyAsync();
Console.WriteLine($"Cluster size: {topology.ClusterSize}");
}
```
#### PinClockAsync(ClockPinRequest, CancellationToken)
```csharp
public Task PinClockAsync(ClockPinRequest body, CancellationToken ct = default)
```
Pin internal clock (alpha)
Set a precise, static time for the Zeebe engine's internal clock.
When the clock is pinned, it remains at the specified time and does not advance.
To change the time, the clock must be pinned again with a new timestamp.
This endpoint is an alpha feature and may be subject to change
in future releases.
| Parameter | Type | Description |
| --------- | ------------------- | ----------- |
| `body` | `ClockPinRequest` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task PinClockExample()
{
using var client = CamundaClient.Create();
await client.PinClockAsync(new ClockPinRequest
{
Timestamp = 1700000000000,
});
}
```
#### ResetClockAsync(CancellationToken)
```csharp
public Task ResetClockAsync(CancellationToken ct = default)
```
Reset internal clock (alpha)
Resets the Zeebe engine's internal clock to the current system time, enabling it to tick in real-time.
This operation is useful for returning the clock to
normal behavior after it has been pinned to a specific time.
This endpoint is an alpha feature and may be subject to change
in future releases.
| Parameter | Type | Description |
| --------- | ------------------- | ----------- |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task ResetClockExample()
{
using var client = CamundaClient.Create();
await client.ResetClockAsync();
}
```
### Resources
#### DeployResourcesFromFilesAsync(string[], string?, CancellationToken)
```csharp
public Task DeployResourcesFromFilesAsync(string[] resourceFilePaths, string? tenantId = null, CancellationToken ct = default)
```
Deploy resources from local filesystem paths.
Reads the specified files, infers MIME types from their extensions,
and calls with the loaded content.
| Parameter | Type | Description |
| ------------------- | ------------------- | ---------------------------------------------------------------------- |
| `resourceFilePaths` | `String[]` | Absolute or relative file paths to BPMN, DMN, form, or resource files. |
| `tenantId` | `String` | Optional tenant ID for multi-tenant deployments. |
| `ct` | `CancellationToken` | Cancellation token. |
**Returns:** `Task` — An with typed access to deployed artifacts.
**Example**
```csharp
public static async Task DeployResourcesFromFilesExample()
{
using var client = CamundaClient.Create();
var result = await client.DeployResourcesFromFilesAsync(
["process.bpmn", "decision.dmn"]);
Console.WriteLine($"Deployment key: {result.DeploymentKey}");
}
```
#### DeleteResourceAsync(ResourceKey, DeleteResourceRequest, CancellationToken)
```csharp
public Task DeleteResourceAsync(ResourceKey resourceKey, DeleteResourceRequest body, CancellationToken ct = default)
```
Delete resource
Deletes a deployed resource. This can be a process definition, decision requirements
definition, or form definition deployed using the deploy resources endpoint. Specify the
resource you want to delete in the `resourceKey` parameter.
Once a resource has been deleted it cannot be recovered. If the resource needs to be
available again, a new deployment of the resource is required.
By default, only the resource itself is deleted from the runtime state. To also delete the
historic data associated with a resource, set the `deleteHistory` flag in the request body
to `true`. The historic data is deleted asynchronously via a batch operation. The details of
the created batch operation are included in the response. Note that history deletion is only
supported for process resources; for other resource types this flag is ignored and no history
will be deleted.
| Parameter | Type | Description |
| ------------- | ----------------------- | ----------- |
| `resourceKey` | `ResourceKey` | |
| `body` | `DeleteResourceRequest` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
**Example**
```csharp
public static async Task DeleteResourceExample(ResourceKey resourceKey)
{
using var client = CamundaClient.Create();
await client.DeleteResourceAsync(
resourceKey,
new DeleteResourceRequest());
}
```
#### GetResourceAsync(ResourceKey, ConsistencyOptions?, CancellationToken)
```csharp
public Task GetResourceAsync(ResourceKey resourceKey, ConsistencyOptions? consistency = null, CancellationToken ct = default)
```
Get resource
Returns a deployed resource.
:::info
This endpoint does not return BPMN process definitions, DMN decision definitions, or form
resources. To query BPMN process definitions or DMN decision definitions, use their
respective APIs.
:::
| Parameter | Type | Description |
| ------------- | ------------------------------------ | ----------- |
| `resourceKey` | `ResourceKey` | |
| `consistency` | `ConsistencyOptions` | |
| `ct` | `CancellationToken` | |
**Returns:** `Task`
#### GetResourceContentAsync(ResourceKey, ConsistencyOptions